6 Eylül 2018 Perşembe

Lambda

Giriş
Lambda'lar C++11 ile dile dahil edildiler. Neden bu kadar geç kalındı sorusu birçoklarının aklındaki bir soru. Lambda dilin bir parçası olduğu için herhangi bir include dosyası gerektirmez.
Değişkene Lambda Atama
auto kullanılarak yapılabilir.
int main()
{
  auto lambda1 = [](){...};
  auto lambda2 = [](){...};
}
std::function kullanılarak yapılabilir.
std::function<void()> fun = [] () {...}
Lambda Çağırmak
Değişkene atanmamıl lamdayı çağırmak için şöyle yaparız.Çıktı olarak 1 alırız.
int x = 0;
[&]{ std::cout << ++x << '\n'; }();
                             // ^^
Lambda ve Parametreleri
C++11 ile lambdanın parametre tipleri birebir yazılmalı.
auto map = [](const vector<int>& in,int x)) {...};
C++14 ile parametre tipi yerine auto kullanılabiliyor.
auto map = [](const auto& in, auto& out, auto f) {...};
Böylece lambda, template gibi tekrar tekrar kullanılabilir hale geliyor.

Değişik Lambda Örnekleri
Parametre almayan boş lambda çağırmak için şöyle yaparız.
[]{}();
Değişkene atanmayan boş lamda tanımlamak için şöyle yaparız. Baştaki süslü parantezler sadece kafa karıştırmak için.
{}[]{};
Bu aslında şu koda eşittir.
{
  // empty scope
}
[]{}; // lambda
Generic Lambda
Generic Lambda yazısına taşıdım.

İçiçe (Nested) Lambda
İyi bir fikir değil ama yapılabilir.
Örnek
Şöyle yaparız.
auto Multiplier1 = []() -> int
{
  auto Multiplier2 = [](int i) -> int
  {
    auto Multiplier3 = [](int i) -> int
    {
      return i * 2;
    };
    return Multiplier3(i * 100);
  };

  int i = 10;
  int ret = Multiplier2(i);
  return ret;
};

int ret = Multiplier1();
std::cout << ret << "\n";
Kendi Kendini Dönen Lambda
Örnek
Şöyle yaparız.
int a = 5;

auto it = [&](auto& self) { // <-- self is now a reference
  return [&](auto b) {
    std::cout << (a + b) << std::endl;
    return self(self);
  };
};
it(it)(4)(6)(42)(77)(999);
Çıktı olarak şunu alırız.
9
11
47
82
1004
Lamda ve Return Type
Normalde lambda için return type yazmayız. Derleyici kendi halledebilir. Aşağıdaki kodda lambda'nın bool döndüğünü anlayabilir.
auto myComparator = ( A lhs,  A rhs)
{
  return lhs.getNumber()  < rhs.getNumber();
};
Ancak bazen derleyici return type'ı anlayamıyor. Bu durumda kodla belirterek derleme hatalarından kurtulmak gerekir.
Kodla Return Type Vermek
Örnek
Aşağıdaki kodda lambda içinde iki lambda kullanılıyor ve derleyici her iki lambdayı farklı tipler olarak gördüğü için return type'a karar veremiyor.
int main() {
    auto f = [] {
        if (1) return [] { return 1; };
        else return [] { return 2; };
    };
    return f()();
}
Dolayısıyla kodla return type -> ile belirtilebilir.
auto f = []() -> std::function<int()> {
    return 1 ? []() { return 1; }
             : []() { return 2; };
};
Örnek
int& tipi vermek için şöyle yaparız.
funct = [](int &i) -> int& {
  ++i;
  return i;
};
Örnek
Elimizde bir yapı olsun
struct Foo {
  vector<string> a;
  vector<string> b;
};
const auto& return type vermek için şöyle yaparız.
[](const Foo& in) -> const auto& {return in.a;}
Örnek
Şöyle yaparız. decltype(auto) return ifadesinin döndürdüğü tipi kullan anlamına gelir.

funct = [](int &i) -> decltype(auto) {
  ++i;
  return i;
};
Lambda Neye Çevrilir
Lambda derleyici tarafından, functor'a çevirilir. Functor struct ile aynıdır. Üretilen struct'a derleyici tuhaf isimle verir. Şuna benzer bir kod üretilir.
struct __uniquely_named_lambda
{
  void operator()() const
  {...}
};
Eğer lambda state yakalamıyorsa boş bir struct oluşturmak ile aynı şeye denk gelir. İyi bir derleyici boş bir struct'ın kurulmasının maliyetini yok eder.Örnek
Aşağıdaki kodda bir normal, bir de static lambda var.
// Classic version
template <class It>
It f(It first, It last)
{
  using value_type = It::value_type;
  auto lambda = [](value_type x){return x > 10 && x < 100;};//normal lambda
  return std::find_if(first, last, lambda);
}

// Static version
template <class It>
It f(It first, It last)
{
  using value_type = It::value_type;
  static auto lambda = [](value_type x){return x > 10 && x < 100;};//static lambda
  return std::find_if(first, last, lambda);
}
Bu iki kod da state yakalamadığı için boş struct kurulmasına sebep olur.

Lambda ve Default Constructor
Açıklaması şöyle. C++17'ye kadar lambda'nın default constructor metodu yoktur.
The closure type associated with a lambda-expression has no default constructor and a deleted copy assignment operator. It has a defaulted copy constructor and a defaulted move constructor.
Şu kod derlenmez.
auto lam=[](int a){ cout<<"a";};
decltype(lam) bb;//oop!deleted default constructor!

Lamda Function Pointer'a Çevrilebilir
Lambda'ının başına +işareti koyarak şöyle yaparız
#include<string>

template<typename T>
void client(T (*func)(const std::string&), const std::string& s) {}

void adaptee_one(const std::string&, int i = 1, char* c = nullptr) {}
void adaptee_two(const std::string&, float* f = nullptr) {}

int main() {
    client (+[](const std::string& s) { return adaptee_one(s); }, "foo");
}
Lambda ve Kalıtım
Aşağıdaki kod derler ancak B nesnesini yaratamayız.
auto a = [](){};

class B : decltype(a)
{
};
Açıklaması şöyle.
The closure type associated with a lambda-expression has no default constructor and a deleted copy assignment operator. It has a defaulted copy constructor and a defaulted move constructor ([class.copy]). [ Note: These special member functions are implicitly defined as usual, and might therefore be defined as deleted. — end note ]
Yani üretilen lambda'nın default constructor metodu yoktur. Kod şuna benzer
//ClosureType() = delete;                     //(until C++14)
ClosureType(const ClosureType& ) = default;   //(since C++14)
ClosureType(ClosureType&& ) = default;        //(since C++14)
Lambda ve Eşitlik
İki lambda aynı koda sahip olsalar birbirlerine eşit değildirler.
auto A = [](){}; auto B = [](){};
birbirlerinden farklı tiplerdir.

Aşağıdaki kodda aynı görünen iki lambdanın farklı olduğu derleyicinin return type'ı bulamamasından anlaşılabilir.
int main() {
  auto f = [] {
    if (1) return [] { return 1; };
    else return [] { return 2; };
  };
  return f()();
}
Lambda ve State Bilgisi
Closure yazısına taşıdım.

Lambda Function Pointer'a Çevrilebilir
Hiçbir şeyi capture etmeyen lambda function pointer olarak kullanılabiliyor.
auto lmda = [](std::ostream& os) -> std::ostream& { os << "hi"; return os; };
std::cout << lmda;
Lambda capture işlemi yapınca kod derlenmiyor.
std::vector<int> v(5, 3);
auto lmda = [&v](std::ostream& os) -> std::ostream& { os << v.size(); return os;};
std::cout << lmda;
Trivial Copy
Lambda trivial copy olmak zorunda değildir. Aşağıdaki kod derleyicisine göre true veya false döndürebilir.
#include <type_traits>
#include <iostream>
using namespace std;

int main()
{
  auto lambda = [](){};

  cout << boolalpha << is_trivially_copyable<decltype(lambda)>{} << endl;
}

Hiç yorum yok:

Yorum Gönder