C++ Dilinde Lambda İfadeleri(2)
- Yusuf Hançar

- Oct 29, 2024
- 28 min read
Updated: Apr 21
Daha önce lambda ifadelerini genel olarak ele alan bir yazımız vardı. Biraz daha derinlemesine incelemek için yeniden değerlendirmeye çalışalım. Lambda ifadeleri, C++11 standardı ile dilin bir parçası olmuştur. Bu ifadeler, adeta diğer dillerdeki nested function yapısının C++ dilindeki bir karşılığı olarak görülmektedir. C ve C++ dillerinde geleneksel olarak bir fonksiyon içinde başka bir fonksiyon tanımlamak mümkün değildir. Lambda ifadeleri, bu eksikliği gidermekle kalmaz, aynı zamanda belirli bir işlevselliği yerel düzeyde tanımlayarak derleyicinin daha iyi optimizasyon yapmasına olanak tanır.
Derleyici, bir lambda ifadesini ele aldığında, ifade karşılığı olarak türü belirli bir class oluşturur. Bu sınıf türü, ifadenin geçici bir nesne olarak ele alınmasını sağlar, yani PR value olarak değerlendirilir. Böylece, bir lambda ifadesi, bir sınıf türü ve geçici bir nesne yapısı olarak kompakt ve hızlı bir çözüm sunar. Yerel düzeyde tanımlandığında, bu sınıf türü bir "local class" haline gelir ve işlevin kapsamı içinde kalır.
Lambda ifadelerinin bir başka kritik avantajı, STL ile gösterdikleri uyumdur. STL algoritmaları, genellikle işlevsel nesneler gerektirir. Lambda ifadeleri bu ihtiyacı karşılamak için oldukça elverişlidir, çünkü kısa ve esnek bir yapıya sahiptirler ve kolaylıkla STL algoritmalarına argüman olarak geçirilebilirler. Hatta set, multiset, map, multimap gibi bazı kaplar (container) için template parametresi olarak kullanıldığında, lambda ifadesinden üretilen bir "closure type" doğrudan bir template argümanı olarak geçirilebilir. Bu uyum, lambda ifadelerinin C++ dilinde geniş bir kullanım alanına sahip olmasının önünü açmıştır.
Genel değerlendirme olarak; C++ dilinde lambda ifadeleri, fonksiyon tanımlamaları ile işlevin çağrılacağı noktalar arasındaki mesafeyi kapatır, derleyiciye optimizasyon açısından esneklik sağlar ve dilin template base programlama yapısına uyumlu bir çözüm sunar.
int gval = 9;
auto fn = [=]{ return gval + 1; };
int main()
{
gval = 50;
std::cout << fn();
}
*****************************
AÇIKLAMA : error: non-local lambda expression cannot have a capture-default
[=] capture kullanıldı, bu da tüm değişkenlerin değerleriyle kopyalanarak yakalanması anlamına gelir. Bu nedenle, fn ifadesi oluşturulurken gval'in o anki değeri olan 9 kopyalanır. Lambda daha sonra çağrıldığında, gval'in güncel değeri olan 50 ile değil, kopyalanan 9 değeri ile çalışır.
*****************************
CEVAP : syntax error
*****************************int gval = 9;
auto fn = [gval = gval]{ return gval + 1; };
int main()
{
gval = 50;
std::cout << fn();
}
*****************************
AÇIKLAMA : lambda init capture(C++14)
*****************************
CEVAP : 10
*****************************int main()
{
auto fn1 = []{ static int x{}; return ++x; };
auto fn2 = []{ static int x{}; return ++x; };
std::cout << fn1() << fn1() << fn1() << "\n";
std::cout << fn2() << fn2() << fn2();
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
123
123
*****************************int main()
{
auto fn1 = []{ int x{}; return ++x; };
auto fn2 = []{ int x{}; return ++x; };
std::cout << fn1() << fn1() << fn1() << "\n";
std::cout << fn2() << fn2() << fn2();
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
111
111
*****************************int main()
{
auto fn = []{ static int sc{}; return ++sc; };
decltype(fn) y;
decltype(fn) z;
std::cout << y() << y() << y() << "\n";
std::cout << z() << z() << z();
}
*****************************
AÇIKLAMA : error: use of deleted function
lambda ifadesi default contructible değildir. bu ifadelerde y ve z ile nesne oluşturulmaya çalışıyoruz ancak fn bu şartı sağlamamaktadır.
Ancak C++20 ile bu durum geçerlidir.
auto olsaydi ?
*****************************
CEVAP : syntax error
*****************************
int main()
{
const int sc = 10;
auto fn = [sc]()mutable { ++sc; };
auto gn = [sc = sc](){ ++sc; };
auto hn = [sc = sc]()mutable{ ++sc; };
}
*****************************
AÇIKLAMA : closer type ta sınıfın veri elemanı const. mutable demek sınıfın veri elemanın non-const olduğu anlamındadır.
gn için mutable eksik. [sc = sc] yapısına lambda init capture denir. Derleyici sınıfa yeni bir veri elemanı koyacak ve sınıfın veri elemanı sc ile initialize edilecek denir. Sınıfın veri elemanının kendisi const değil ve burda constluk düşer ancak sınıfın üye fonksiyonu const olduğu için ve const üye fonksiyon içinde sınıfın veri elemanını değiştirme çabası syntax error
*****************************
CEVAP : "fn" ve "gn" error "hn" legal
*****************************int main ()
{
int sc{ 4 };
auto fn = [sc = sc + 1, &r = sc]()
{
r += 2;
return sc * sc;
}();
std::cout << "fn : " << fn << "\n";
std::cout << "sc : " << sc;
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
fn : 25
sc : 6
*****************************int gval = 5;
int main ()
{
auto fn = [](int sc = ++gval){ return sc * sc; };
auto x = fn();
auto y = fn();
std::cout << x << ' ' << y << ' ' << gval;
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : 36 49 7
*****************************double sc{};
int main ()
{
auto fn = [sc = 0]()-> decltype(sc){ return 1; }();
std::cout << std::is_same_v<decltype(fn), int>;
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : 0
*****************************
Capture/Standart | C++11/C++14 | C++17 | C++20 |
[this] | &(*this) | &(*this) | &(*this) |
[*this] | X | *this | *this |
[this, *this] | X | X | X |
[&] | implicit &(*this) | implicit &(*this) | implicit &(*this) |
[&, this] | &(*this) | &(*this) | &(*this) |
[&, *this] | X | *this | *this |
[=] | implicit &(*this) | implicit &(*this) | Deprecated implicit &(*this) |
[=, this] | X | X | &(*this) |
[=, *this] | X | *this | *this |
Lambda ifadesinden bir fayda sağlayabilmek için
Doğrudan ifadenin kendisini ilgili sınıfın fonksiyon çağrı operator fonksiyonuna (lambda function) argüman göndermektir.
int main ()
{
[](){};
}
*****************************
AÇIKLAMA : burda fonksiyonun parametre değişkeni yok () yazmasak da geçerlidir. Bu durumda bir fonksiyonun çağırılması sağlanır.
Derleyicinin oluşturduğu sınıfın fonk. çağrı op. fonksiyonu [](){} olarak oluşturulan PR value nesne için çağrılır.
***************************** int main ()
{
[](int sc){ return sc * 6; };
}
*****************************
AÇIKLAMA : Bir fonksiyon çağrısı olmaz, bu sadece bir geçici nesnedir.
; ile ömrü biten bir geçici nesnedir.
***************************** Lambda ifadesinden bir fayda sağlayabilmek için
Doğrudan ifadenin kendisini ilgili sınıfın fonksiyon çağrı operator fonksiyonuna (lambda function) argüman göndermektir.
int main ()
{
[](int sc){ return sc * 6; }(10);
}Bu fonksiyonu birden fazla kez çağırmak istiyorsak derleyicinin oluşturduğu closer object isimlendirilebilir. Sonuçta bir PR value expression olduğu için maliyeti yoktur. (mandatory copy elision) Bu sayede isimlendirilmiş bir function object haline getirilebilir. Aynı zamanda kodun okunabilirliğini artırır. Ancak const olmasına dikkat edilmelidir.
int main ()
{
auto fn = [](int sc){ return sc * 6; };
fn(10);
}Bu PR value nesneyi bir fonksiyona arguman olarak geçmektir. Arguman olarak gönderdiğimiz fonksiyonun parametre değişkeninin [](int x){ return x * 6; }; şeklinde olması gerekir. Genelde türünü bilmediğimiz için çoğunlukla kullanım senaryosu function template ile çıkarım yapılmasıdır. STL deki bazı algoritmalar unary ya da binary predicate almaktadır. Onlara da lambda ifadesi geçilir. (transform, copy, copy_if)
template <typename T>
void func(T){}
int main ()
{
func([](int sc){ return sc * 6; });
}
*****************************
AÇIKLAMA :
[](int sc){ return sc * 6; } ifadesinden derleyici bir closer type oluşturur. CTAD ile bunun tür çıkarımını yapar.
func fonksiyonu için yazılan specialization template arguman olarak closer type türünden olur.
***************************** Bazı yerlerde lambda ifadesine karşılık gelen closer type türünün özellikle bilinmesi gerekmez. Çünkü bir fonksiyon ya da fonksiyon şablonuna arguman olarak gönderilir ve deduction yapılır. Ancak bazı senaryolarda ise bu tür bilgisinin de kullanılması gerekmektedir. Bu tür bilgisini elde etmenin yolu ise decltype specifier kullanmaktır.(C++20 öncesi)
int main ()
{
auto fn = []{};
decltype(fn)sc;
}
*****************************
AÇIKLAMA : fn derleyicinin oluşturduğu sınıf türünden bir closer objecttir.
Bu closer type decltype specifier tarafından parametre olarak alınır ve türü elde edilir.
Artık bu tür bir sınıf şablonunun tür parametresi için template argument olarak kullabılabilir.
Ya da bu türden bir değişken tanımlanabilir. (c++20 avantajları ile)
***************************** typeid
int main ()
{
auto fn = [](){};
std::cout << typeid(fn).name();
}
*****************************
AÇIKLAMA : derleyici tarafından oluşturulan isimlendirmesi de derleyici tarafından yapılan ve taşınabilirliği olmayan type_info sınıfının name() fonksiyonunun geri dönüş değeri.
***************************** Derleyiciler her lambda ifadesi için farklı closer type oluşturur.
int main ()
{
auto fn1 = [](int sc){ return sc * 5; };
auto fn2 = [](int sc){ return sc * 5; };
std::cout << std::boolalpha << std::is_same_v<decltype(fn1), decltype(fn2)>;
}
*****************************
AÇIKLAMA : fn1 ve fn2 kesinlikle farklı türlerdir.
*****************************
CEVAP : false
*****************************Bir closer object başka bir closer object ile copy construct edilebilir.
int main ()
{
auto fn1 = [](int sc){ return sc * 5; };
auto fn2 = fn1;
//decltype(fn1) fn2 = fn1;
std::cout << std::boolalpha << std::is_same_v<decltype(fn1), decltype(fn2)>;
}
*****************************
AÇIKLAMA : fn1 ve fn2 türü aynıdır.
Türünü bilmediğimiz için auto ya da decltype type deduction kullabiliriz.
*****************************
CEVAP : true
*****************************auto type deduction mekanizmasında görsel olarak karıştırılan noktalardan biri de closer type üzerinden çıkarım yapmak ile çağrılan fonksiyonun geri dönüş değerinden çıkarım yapma farkıdır.
int main ()
{
auto fn1 = [](int sc){ return sc * 5; };
auto fn2 = [](int sc){ return sc * 5; }(10);
}
*****************************
AÇIKLAMA :
fn1 closer type türündendir.
fn2 ise çağrılan fonksiyonun geri dönüş değeri türündendir.(bu örnekte int) -> (auto fn2 = fn1(10) gibi)
***************************** Derleyiciye yazdırdığımız sınıfın fonksiyon çağrı operator fonksiyonun ana bloğu ve bu fonksiyonun default olarak const üye fonksiyon olduğu bilinmelidir. lambda function içinde bir return statement yok ise derleyicin yazdığı lambda function geri dönüş değeri void olur.
class Data {
public :
void operator()(int sc) const
{
}
};
int main ()
{
auto fn = [](int val){};
}
*****************************
AÇIKLAMA : Data sınıfı içindeki gibi bir fonksiyon çağrı operator fonksiyonu yazar.
***************************** class Data{
public :
void operator()(int sc)
{
}
};
int main ()
{
auto fn = [](int val)mutable{};
}
*****************************
AÇIKLAMA : mutable yazmak çağrı operator fonksiyonunda const u kaldırır.
***************************** Fonksiyonun tür çıkarımını return ifadesinden yaptı ancak fonksiyon içerisinde multiple return statement olabilir. Bunlar farklı türden de olabilir. Bu durumda ya return statement türleri aynı olacak ya da tür çıkarımını derleyiciye bırakmayacağız. (trailing return type ile)
int main ()
{
auto fn = [](int sc){
if (sc > 10)
{
return 3;
}
else
{
return 4.5; // syntax error
}
};
}
*****************************
CEVAP : syntax error
*****************************int main ()
{
auto fn = [](int sc)->double{
if (sc > 10)
{
return 3;
}
else
{
return 4.5;
}
};
}int main ()
{
auto fn = [](int sc) {
if (sc > 10)
{
return 'a';
}
else
{
return 4;
}
};
}
*****************************
AÇIKLAMA : char to int promotion var ancak yine de error.
*****************************
CEVAP : syntax error
*****************************lambda ana blok içerisinde değişken tanımlanabilir.
int main ()
{
[]() {
static int sc = 10;
};
}int main ()
{
auto fn = []() {
static int sc = 10;
++sc;
std::cout << sc << "\n";
};
}
*****************************
AÇIKLAMA : fonksiyon çağrı operator çağrılmadı.
*****************************
CEVAP : standart output bişey yazdırmaz.
*****************************int main ()
{
auto fn = []() {
static int sc = 10;
++sc;
std::cout << sc << "\n";
};
fn();
fn();
fn();
}
*****************************
CEVAP :
11
12
13
*****************************c++11 ile default argument alma yoktu. C++14 ile dile eklendi.
int main ()
{
auto fn = [](int sc = 10) {
std::cout << sc << "\n";
};
fn(10);
fn();
}
*****************************
CEVAP :
10
10
*****************************generalized lambda expression
Derleyiciler sınıfın kodunu yazarken fonksiyon çağrı operator fonksiyonunu normal olarak yazar.
C++14 ve sonrasında bu closer type'ın fonksiyon çağrı op. fonksiyonunu bir member template olarak yazar. auto bu işi yapar.
class Data {
public :
template <typename T>
T operator()(T sc)const
{
return sc * sc;
}
};
int main ()
{
auto fn = [](auto val) { return val * val; };
}int main ()
{
auto fn = [](auto x, auto y) {};
fn(10, 5.4);
}generalized lambda ve perfect forwarding
int main()
{
auto fn = [](auto&& pf) {
std::forward<decltype(pf)>(pf);
};
}int main()
{
auto fn = [](auto&&... args) {
((std::cout << args << " "), ...);
};
fn(11, 14.17, "->cpp");
return 0;
}
void foo(std::string&)
{
std::cout << "L value ref" << "\n";
}
void foo(const std::string&)
{
std::cout << "const L value ref" << "\n";
}
void foo(std::string&&)
{
std::cout << "R value ref" << "\n";
}
int main()
{
auto fn = [](auto&& pf) {
foo(std::forward<decltype(pf)>(pf));
};
std::string str{ "smartcode" };
fn(str);
return 0;
}
*****************************
CEVAP : L value ref
*****************************
template <typename ...Args>
void show(Args&& ...args)
{
std::initializer_list<int>{ ((std::cout << std::forward<decltype(args)>(args) << std::endl), 0)... };
}
int main()
{
auto fn = [](auto&& ...args) {
show(std::forward<decltype(args)>(args)...);
};
fn(11, "cpp", 14.17, std::string("modern"));
return 0;
}
*****************************
AÇIKLAMA : universal reference parametreye de sahip olabilir.
*****************************
CEVAP :
11
cpp
14.17
modern
*****************************
familiar template syntax
C++20

int main ()
{
auto fn = []<typename T>() {};
}namespace detail {
template <typename F, typename Tuple, std::size_t... Is>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t, std::index_sequence<Is...> {
return std::invoke(std::forward<F>(f), std::get<Is>(std::forward<Tuple>(t))...);
}
}
template <typename F, typename Tuple>
constexpr decltype(auto) apply_impl(F&& f, Tuple&& t> {
return detail::apply_impl(
std::forward<F>(f), std::forward<Tuple>(t),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_v<Tuple>>>());
}
*****************************
AÇIKLAMA : we need to delegate to another function just to destructure the template argument.
***************************** template <typename F, typename Tuple>
constexpr decltype(auto) apply(F&& f, Tuple&& t)
{
return [&](auto... Is>(std::index_sequence<Is...>) {
return std::invoke(std::forward<F>(f),
std::get<Is>(std::forward<Tuple>(t))...);
}(std::make_index_sequence(std::tuple_size_v<std::remove_reference_t<Tuple>>>());
}
*****************************
AÇIKLAMA : c++20
A familiar template syntax immediately-invoked lambda expression(FTSIILE)
***************************** template <typename Tuple, typename F>
F for_each(Tuple&& t, F&& f)
{
return std::apply(
[&]<class... Ts>(Ts&&... ts){ (f(std::forward<Ts>(ts)), ...); ),
std::forward<Tuple>(t), f;
}
*****************************
AÇIKLAMA : c++20
But it gets tricker if we want a binary function over 2 tuples, or an n-ary function over multiple tuples.
***************************** template <typename Tuple1, typename Tuple2, std::size_t... Is>
F for_each_impl(Tuple1&& t1, Tuple2&& t2, F&& f, std::index_sequence<Is...>)
{
return (std::invoke(f, std::get<Is>(std::forward<Tuple1>(t1)),
(std::invoke(f, std::get<Is>(std::forward<Tuple2>(t2))), ...), f;
}
template <typename Tuple1, typename Tuple2, typename f>
constexpr decltype(auto) for_each(Tuple1&& t1, Tuple2&& t2, F&& f)
{
return for_each_impl(std::forward<Tuple1>(t1), std::forward<Tuple2>(t2),
std::forward<F>(f),
std::make_index_sequence<std::tuple_size<
std::remove_reference_t<Tuple1>>::value>());
}template <typename Tuple1, typename Tuple2, typename F>
constexpr decltype(auto) for_each(Tuple1&& t1, Tuple2&& t2, F&& f)
{
return [&] <std::size_t... Is> (std::index_sequence<Is...>) {
return (std::invoke(f, std::get<Is>(std::forward<Tuple1>(t1)),
std::get<Is>(std::forward<Tuple2>(t2))), ...), f;
}(std::make_index_sequence<std::tuple_size<std::remove_reference_t<Tuple1>>::value>());
}
*****************************
AÇIKLAMA : c++20
use reference capture and variadic template head
for_each on tuples with FTSIILES
***************************** If you are delegating to an ancillary function just to destructure a type, consider a Familiar Template Syntax Immediately-Invoked Lambda Expression.
No more decltype
Reference capture provides automatic value category preservation
Simpler argument handling
Especially useful for destructing with "std::index_sequence"
capture clause
Derleyiciye lambda ile bir kod yazdırılır.
Bu sınıfa bir fonksiyon çağrı operator fonksiyonu ekler, parametre parantezi içinde auto kullanınca bu member template haline gelir. Ya da c++20 ile gelen familiar template ile yine member template haline getirilebilir.
Ancak ilave bir sentaks öğesi kullanılmazsa derleyicinin yazdığı sınıfın veri elemanlarının olmasını sağlayamayız.
class Lambda {
public :
Lambda(int val) : m_val{ val }{}
bool operator()(int sc) const
{
return sc > m_val;
}
private:
int m_val;
};
int main ()
{
Lambda{ 10 };
}
*****************************
AÇIKLAMA : burada karşılaştırma sc değerinin 10 değerinden büyük olmasını sınar. Ancak 10 değerinin de lambda ifadesini kullanan kod tarafından belirlenmesini istersek sınıfı kendimiz yazıyor olsak sınıfa bir adet veri elamanı eklenir. Bu veri elemanı ctor ile init edilir.
***************************** Derleyiciye yukarıdaki gibi bir kod yazdırmak için capture clause kullanılır.
Burada sınıfa eklenicek veri elemanları reference olmayan herhangi bir türden olabilir.
Bazen de yazdırılan sınıfın veri elemanının sol taraf referansı olmasını bazı avantajlar sağlamak için isteriz. Yani kopyalamadan kaçınarak reference semantiğe geçmek içindir.
Mesela basit bir işlem için bir std::vector container'ı eleman olarak alıp std::vector'un kopyalanmasını sağlayalım ?
Sınıfı kendimiz yazıyor olsak "vector<int> &r" referans yaparak contructorda bu referansı vector<int> ile init ederek kopyalamadan kaçınmış olunur. Bunun nedenlerinden biri move-only type olabilir. Ancak referans semantiği ile aynı nesneye bağlanabiliriz. Bunu yapmanın yolu da capture clause.
class Lambda {
public :
Lambda(){}
private:
std::vector<int> &rvec;
};İçeride static ömürlü değişkenler doğrudan kullanılabilir. Onlar için bir capture oluşturmaya gerek yoktur ve syntax error olur.
int gval = 10;
int main()
{
auto fn = [](int sc) {
return sc * gval; // legal
};
}
*****************************
AÇIKLAMA : static ömürlü değişkenleri capture edemeyiz.
Nedeni de bu bir local class olduğu için { // code } içerisinde zaten visible durumdadır.
*****************************int gval = 10;
int main()
{
auto fn = [gval](int sc) {};
}
*****************************
AÇIKLAMA : gval capture edilemez.
*****************************
CEVAP : syntax error
capture of variable ‘gval’ with non-automatic storage duration
*****************************int main()
{
static int gval = 10;
auto fn = [gval](int a) {};
}
*****************************
AÇIKLAMA : gval capture edilemez.
*****************************
CEVAP : syntax error
capture of variable ‘gval’ with non-automatic storage duration
*****************************int gval = 10;
int main()
{
auto fn = [=](int sc) {};
}
*****************************
AÇIKLAMA : herşey kopyalama yoluyla capture edildi geçerli ancak gval burda capture edilmiş değildir. gval değişkenini kullanmasının nedeni capture all değildir.
*****************************
CEVAP : legal
*****************************int main()
{
int val{ 12 };
auto fn = [=](int sc) {
return sc * val;
};
}
*****************************
AÇIKLAMA : val capture edilmeden kullanılamaz.
Çözüm sınıfa bir veri elemanı koyulur derleyici yazdığı kodda sınıfın veri elemanını val ile initialize eder.
*****************************
CEVAP : legal
*****************************int main()
{
int val{ 12 };
auto fn = [](int sc) {
return sc * val;
};
}
*****************************
AÇIKLAMA :
*****************************
CEVAP : syntax error
error: ‘val’ is not captured
*****************************int main()
{
const int val{ 12 };
auto fn = [](int sc) {
return sc * val;
};
}
*****************************
AÇIKLAMA : istisna bir durumdur!!!
*****************************
CEVAP : legal
*****************************int main()
{
int val{ 12 };
auto fn = [val](int sc) {
return sc * val;
};
}
*****************************
AÇIKLAMA : sc * val ifadesinde kullanılan val int val{ 12 } değil sınıfın veri elemanı olandır. bu val derleyicinin yazdığı ctor da int val{ 35 } ile initialize edilir.
*****************************
CEVAP : legal
*****************************copy capture
Birden fazla değişkeni kopyalama yoluyla capture etmek istersek yöntemlerden biri değişkenlerin isimlerinin virgüllerle ayrılarak yazılmasıdır.
int main()
{
int x, y, z;
auto fn = [x, y, z]() {};
}Diğer yöntem capture all kullanmaktır.
int main()
{
int x, y, z;
auto fn = [=]() {};
}int main()
{
int x, y, z;
auto fn = [x, y]() {
// z ...
};
}
*****************************
AÇIKLAMA : z capture edilmedi ancak kullanılmaya çalışıldı
[=] yaparsak yerel değişkenlerin hepsi visible olur.
*****************************
CEVAP : syntax error
*****************************Fonksiyonun ana bloğu içinde kullanılan "arr" array-decay olmadığı için bir dizidir pointer değildir.
int main()
{
int arr[4]{};
auto fn = [arr]() {
std::is_same_v<decltype(arr), int*>;
};
}
*****************************
CEVAP : false
*****************************int main()
{
int arr[4]{};
auto fn = [arr]() {
is_same_v<decltype(arr), int[4]>;
};
}
*****************************
CEVAP : true
*****************************int main()
{
int arr[4]{};
auto fn = [arr]() {
for (auto val : arr)
{
}
};
fn();
}
*****************************
AÇIKLAMA : pointer olsaydı zaten for loop hatalı olurdu.
pointer range based for loop ile kullanılamaz.
***************************** lambda init capture
C++14
[arr = arr]
Daha çok taşıma semantik ile ilgilidir.
Örneğin bir move only type taşınarak capture edilmek istendiğinde kullanılır.
int main()
{
int arr[4]{};
auto fn = [arr = arr]() {
std::is_same_v<decltype(arr), int*>;
};
}
*****************************
AÇIKLAMA : .. = arr ifadesi ile initialize ediliyor
*****************************
CEVAP : true
*****************************int main()
{
const int val = 10;
auto fn = [val = val]()mutable{
++val;
};
}
*****************************
AÇIKLAMA : sınıfın veri elemanı const yapmak istemiyorsak çıkarımda constluk düşer ve sınıfın veri elemanı int olur.
***************************** int main()
{
auto uptr = std::make_unique<std::string>(100, 'a');
auto fn = [uptr](){};
}
*****************************
AÇIKLAMA : move only type olduğu için
error: use of deleted function
*****************************
CEVAP : syntax error
*****************************int main()
{
auto uptr = std::make_unique<std::string>(100, 'a');
auto fn = [&uptr](){};
fn();
std::cout << (uptr ? "full" : "empty") << "\n";
}
*****************************
AÇIKLAMA : fonksiyonun kodu içinde taşıma yapılmadığı çıkarımı var
*****************************
CEVAP : full
*****************************uptr taşınarak capture edilmek istenirse yani sınıfın veri elemanına taşınması istenirse :
int main()
{
auto uptr = std::make_unique<std::string>(100, 'a');
// ilk uptr bildirilen isim, std::move(uptr) olan ise local scope ta aranır.
auto fn = [uptr = std::move(uptr)](){};
fn();
std::cout << (uptr ? "full" : "empty");
}
*****************************
CEVAP : empty
*****************************int main()
{
auto up = std::make_unique<std::string>("smart");
auto fn = [up = std::move(up)](){ return *up + " code"; };
std::cout << fn() << "\n";
if (up)
{
std::cout << "full" << "\n";
}
else
{
std::cout << "empty" << "\n";
}
return 0;
}
*****************************
AÇIKLAMA : burda copy capture kullanılamaz, reference semantik kullanınca da taşımış olmayız.
up taşındığı için boş oldu.
*****************************
CEVAP :
smart code
empty
*****************************struct Data {
auto foo() const
{
// fonksiyonun return type ı closer object
return [sc = sc](){ std::cout << sc << std::endl; };
}
std::string sc;
};
int main()
{
const auto f1 = Data{ "smart" }.foo(); // f1 ve f2 de closer type türündendir
const auto f2 = Data{ "code" }.foo();
f1();
f2();
return 0;
}
*****************************
AÇIKLAMA : C++11 de auto return type yoktu ve lamda döndürebilmek için std::function return type yapılırdı. [sc] şeklinde capture edilemez.
*****************************
CEVAP :
smart
code
*****************************lambda içinde nesnenin kendisini kullanmak istersek :
kopyalama maliyetinden kaçınmak için
ya da nesneyi çağrılan fonksiyon içinde değiştirmek için (non-const üye fonksiyonunu çağırmak)
bu durumda capture reference yoluyla yapılmalıdır.
int main()
{
int val{ 5 };
auto fn = [val]() {
val = 15;
};
}
*****************************
AÇIKLAMA : değiştirmeye yönelik
*****************************
CEVAP : syntax error
*****************************int main()
{
int val{ 5 };
auto fn = [val]()mutable {
val = 15;
};
}
*****************************
AÇIKLAMA : fonksiyonun const olmamasını sağladık.
*****************************
CEVAP : legal
*****************************int main()
{
int val{ 5 };
auto fn = [&val]() {
++val;
};
fn();
std::cout << val << "\n";
}
*****************************
AÇIKLAMA : derleyici sınıfın veri elemanını int& yapacak ve onu da bu variable ile constructor initialize edilir.
*****************************
CEVAP : 6
*****************************int main()
{
int val{ 5 };
auto fn = [&val]() {
++val;
};
val = 10;
fn();
std::cout << val << "\n";
}
*****************************
AÇIKLAMA : val bir reference fonksiyonu çağırınca val değeri artırılır.
*****************************
CEVAP : 11
*****************************reference ile yakaladık ancak mutable yapmadan değeri bir pointer olduğu için değiştirebildik.
class Data {
public :
void func() const
{
int ival{ 35 };
m_iptr = &ival; // syntax error
}
private :
int* m_iptr;
};
*****************************
AÇIKLAMA : const üye fonksiyon ve sınıfın veri elemanın değiştirme çabası
*****************************
CEVAP : syntax error
*****************************class Data {
public :
void func() const
{
int ival{ 35 };
*m_iptr = ival; // legal
m_r = ival; // legal
}
private :
int* m_iptr;
};
*****************************
AÇIKLAMA : const üye fonksiyon olmasıyla ilgili bir işlem yapılmadı.
*****************************
CEVAP : legal
*****************************değişkenlerin bir kısmını reference bir kısmını kopyalama yoluyla capture edebiliriz.
int main()
{
int x{}, y{}, z{};
auto fn = [&x, y, z]() {
};
}Değişkenlerin reference yoluyla capture ederken dikkatli olmalıyız. gerekmiyorsa kaçınılmalıdır. Bunu kullanıyorsak eğer niyetimiz belli olmalıdır.
ya nesne değiştirilecek
ya da kopyalama maliyeti değiştirilecek
Aksi durumda farkında olmadan dangling reference oluşabilir.
"Dangling reference" terimi, bir referansın geçerliliğini yitirdiği durumu ifade eder. Bu durum, bir nesnenin yaşam süresinin sona erdiği halde, ona referans aracılığıyla erişmeye çalışıldığında ortaya çıkar. Referans, başka bir nesneye yönlendirildiği durumda veya referansı tutan nesne bellekten serbest bırakıldığında dangling reference oluşur.
int main()
{
int sc = 42;
int& ref = sc; // sc'nin referansını oluşturuyoruz
std::cout << "sc: " << sc << ", ref: " << ref << std::endl;
{
int val = 100;
ref = val; // sc'nin referansını val'e yönlendiriyoruz
// Burada val'in yaşam süresi sona eriyor
} // val'in yaşam süresi burada sona eriyor
// Dangling reference: ref, artık geçerli bir nesneye (yok olan 'val') referans yapar
std::cout << "sc: " << sc << ", ref: " << ref << std::endl;
return 0;
}Bir fonksiyonun geri dönüş değeri türü de closer type olabilir.
auto foo()
{
int sc{10};
return sc; // geri dönüş değeri türü int olur.
}auto foo()
{
int sc{10};
return ≻
}
*****************************
AÇIKLAMA : pointer hatası olur. otomatik ömürlü bir nesnenin adresi döndürülemez.
*****************************
CEVAP : warning: address of local variable ‘x’ returned [-Wreturn-local-addr]
*****************************auto foo()
{
std::vector ivec{ 5, 6, 8, 10 };
return ivec;
}auto foo(int idx)
{
auto fn = [idx](int sc){ return sc * idx; };
return fn;
}
int main()
{
auto fn = foo(10);
auto val = fn(15);
std::cout << val;
}
*****************************
AÇIKLAMA : kopyalama yoluyla olduğundan herkes kendi kopyasına sahiptir.
*****************************
CEVAP : 150
*****************************auto foo(int idx)
{
auto fn = [&](int sc){ return sc * idx; };
return fn;
}
int main()
{
auto fn = foo(10);
auto val = fn(15); // dangling reference
std::cout << val;
}
*****************************
AÇIKLAMA : reference capture edilseydi
burda oluşturulan sınıf nesnesinin veri elemanı olan reference burdaki fonksiyonun parametre değişkenine referans olur.
Ve bu fonksiyonun parametre değişkeninin hayatı bitmiş olur.
Artık fn kullanıldığında dangling reference olmuş olacaktır ve tanımsız davranıştır.
*****************************
CEVAP : 150
*****************************deep copy maliyeti ?
int main()
{
std::vector<int> ivec(100, 1);
auto fn = [ivec]() {
// ivec...
};
}
*****************************
AÇIKLAMA : copy capture olduğundan vector fiilen kopyalanır.
deep copy maliyeti vardır.
***************************** int main()
{
std::vector<int> ivec(100, 1);
auto fn = [&ivec]() {
// ivec...
};
}
*****************************
AÇIKLAMA : reference capture
deep copy maliyeti yoktur.
***************************** sınıfın non-static üye fonksiyonları içinde capture yapılması
struct Lambda {
int mx{}, my{};
void foo()
{
int z = 10;
auto fn = [](int val) {
return val * z; // syntax error z yerel değişken
};
}
};struct Lambda {
int mx{}, my{};
void foo()
{
int z = 10;
auto fn = [z](int val) {
return val * z; // legal z capture edildi.
};
}
};struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [](int val) {
return val * mx; // visible değil capture etmeden kullanamayız.
};
}
};struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [mx](int val) { // sınıfın veri elemanı bu şekilde capture edilmez.
return val * mx;
};
}
};struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [this](int val) {
return val * mx; // legal
};
}
};struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [=](int val) {
return val * mx;
};
}
};
*****************************
AÇIKLAMA : copy all ile olabilir.
c++20 ile deprecated edildi ve ayrıca tehlikeli bir kullanımdır.
*****************************
CEVAP : legal
*****************************this pointer'ın kopyalama yoluyla ya da reference yoluyla capture edilmesi arasında fark yoktur.
struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [=](int val) {
++mx; // sınıfın veri elemanı değiştirilmiş olur.
};
fn(10);
}
};
*****************************
AÇIKLAMA : normalde kopyalama semantiğinde bir değişiklik yapıldığında nesne etkilenmemesi gerekir.
this yazılması tavsiye edilir.
*****************************
CEVAP : c++20 de kaldıırldı.
*****************************struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [=, this](int val) {
++mx;
};
fn(10);
}
};
*****************************
AÇIKLAMA : c++20 ile eklendi
[=, this]
özellikle this pointer da kopyalama yoluyla capture edilmek istenirse
***************************** Fonksiyon içinde nesnenin kendisinin yerine kopyasını kullanmak istersek ve onun üzerinde değişiklik yapmak istersek lambda init capture ile yapılırdı.
struct Lambda {
int mx{}, my{};
void foo()
{
auto fn = [copy_this = *this]() { // init capture syntax
copy_this.mx;
};
}
};
*****************************
AÇIKLAMA : derleyici bir sınıf kodu yaz *this türünden olsun. onu da *this ile initialize et. copy_this *this kopyası olacaktır.
***************************** struct Lambda {
int mx{}, my{};
auto foo()
{
auto fn = [copy_this = *this]() {
///...
};
return fn; // dangling reference problemi olmayacaktır.
}
};struct Lambda {
int mx{}, my{};
auto foo()
{
auto fn = [*this]() {
auto val = mx; // legal [copy_cap = *this] yazmaya gerek kalmadı.
};
return fn;
}
};forwarding reference ve lambda ifadesi
void func()
{
auto fn = [](auto&& ...sc) { // parametre paketi forwarding reference
};
}
*****************************
AÇIKLAMA : istenilen kadar parametre gönderilebilir.
***************************** C++17 standartı ile lambda fonksiyonların constexpr olması kuralı değiştirildi.
constexpr default değildi.
c++17 ile constexpr koşulu ihmal edilmediği sürece lambda fonksiyonları da derleyici tarafından constexpr olarak ele alınır.
void func()
{
[](int sc) { return sc * 5; };
}
*****************************
AÇIKLAMA :
-> static ömürlü değişken olmayacak
-> fonksiyonun tüm parametreleri, fonksiyon içindeki yerel değişkenler, fonksiyonun geri dönüş değeri türü literal type olmalıdır.
***************************** int main()
{
auto fn = [](int sc) { return sc * 5; };
constexpr auto val = fn(12);
}
*****************************
AÇIKLAMA :
constexpr değişkene ilk değer veren ifade compile time sabiti olmalıdır.
fn(12) compile-time sabiti olmasaydı syntax error olurdu.
auto fn = [](int x)constexpr { return x * 5; }; şeklinde yazmadık ama default öyle algılandı. (c++17)
***************************** int main()
{
auto fn = [](int sc) {
static int val{ 10 };
++val;
return sc * 5;
};
}int main()
{
auto fn = [](int sc) {
static int val{ 10 };
++val;
return sc * 5;
};
auto val = fn(12);
}
*****************************
AÇIKLAMA : constexpr olma koşulunu ihlal etti. static yerel değişken var.
*****************************
CEVAP : legal
*****************************int main()
{
auto fn = [](int sc) constexpr {
static int val{ 10 };
++val;
return sc * 5;
};
}
*****************************
AÇIKLAMA : constexpr olma koşulunu ihlal etti direk syntax error oldu.
*****************************
CEVAP : syntax error
*****************************lambda ifadeleri için noexcept default tanımlı değildir.
int main()
{
auto fn = [](int sc) { return sc * sc; };
// noexcept operatorunun operandı olan ifade içinde unevaluated context oluşturur.
constexpr auto val = noexcept(fn(15));
}
*****************************
AÇIKLAMA : exception throw etmeme garantisi vermez.
*****************************
CEVAP : false
*****************************int main()
{
auto fn = [](int sc) noexcept { return sc * sc; };
constexpr auto val = noexcept(fn(15));
}
*****************************
AÇIKLAMA : exception throw etmeme garantisi verir.
*****************************
CEVAP : true
*****************************template <typename T>
void func(T&& fn)
{
if constexpr (std::is_nothrow_invocable_v<T, int>)
{
std::cout << "Calling fn(10) with optimisation" << "\n";
fn(10);
}
else
{
std::cout << "Calling fn(10) normally" << "\n";
fn(10);
}
}
int main()
{
int val{ 10 };
const auto fn1 = [&val](int sc) noexcept { val += sc; };
func(fn1);
const auto fn2 = [&val](int sc) { cout << "fn2 with val = " << val << endl; val += sc; };
func(fn2);
}
*****************************
AÇIKLAMA : callable nesnenin noexcept ile işaretlenip işaretlenmediğini kontrol etmek için std::is_nothrow_invocable_v'yi kullanır.
*****************************
CEVAP :
Calling fn(10) with optimisation
Calling fn(10) normally
fn2 with val = 20
*****************************std::function herhangi bir callable sarmalayabilir.
lambda ifadesi ile de bir callable oluşturulabilir. Derleyicinin lambda karşılığı yazdığı da functor classtır.
std::function hem bir bellek overhead durumu vardır(lambda bir std::function ile sarmalandığında fazladan bir bellek alanı kullanılır) hem de bazı durumlarda dinamik bir bellek alanı allocate edilir.
int main()
{
auto fn = [](double d){ return d * d + .3; };
std::cout << "sizeof(decltype(fn)) : " << sizeof(decltype(fn)) << "\n";
}
*****************************
AÇIKLAMA : herhangi bir veri elamanı yok zaten. adres elde edebilmesi içinde bir storage a sahip olmalıdır. sıfır olamaz bu yüzden.
*****************************
CEVAP : sizeof(decltype(fn)) : 1
*****************************#include <functional>
int main()
{
auto fn = [](double d){ return d * d + .3; };
std::cout << "sizeof(decltype(fn)) : " << sizeof(decltype(fn)) << "\n";
std::function fnc = fn; // CTAD ile böyle yazabildik
std::cout << "sizeof(fnc) : " << sizeof(fnc) << "\n"; // 40 (derleyiciye bağlı)
std::cout << fn(5.45) << " " << fnc(5.45) << "\n";
}
*****************************
AÇIKLAMA :
*****************************
CEVAP :
sizeof(decltype(fn)) : 1
sizeof(fnc) : 32
30.0025 30.0025
*****************************size() fonksiyonu yerine std::size kullanılan senaryo ?
int main()
{
[](const auto& c) {
return c.size();
};
} int main()
{
[](const auto& c) {
return std::size(c);
};
}
*****************************
AÇIKLAMA : C array söz konusu olduğunda gereklidir.
size global bir fonksiyon olduğundan ve üye fonksiyonu olmadığından onun partial specialization'ı çağırılır.
***************************** lambda ifadesinin en büyük avantajı local düzeyde olmasıdır. Adeta local bir fonksiyon oluşturulur(diğer dillerdeki nested function gibi)
C++ dilinde bir diğer kısıtlama ise template local düzeyde olamaz. Bazı durumlarda template ifadesi yerine lambda yazabiliriz ancak template yazabilmemiz için global düzeyde olmalıyız.
inline olduğundan derleyici tarafından inline expansion yapılabilir ve o contexteki diğer öğelerle birleştirip daha etkili optimizasyon yapabilir.
Ayrıca run-time da configure edilebilir.(capture mekanizması ile)
int main()
{
std::vector<int> ivec(30);
std::list mylist{ 12, 5.4 };
std::cout << std::size(ivec) << "\n";
std::cout << std::size(mylist) << "\n";
int arr[20]{};
std::cout << std::size(arr);
}
*****************************
CEVAP :
30
2
20
*****************************int main()
{
auto ptr = new int[]{ 2, 4, 6 };
}
*****************************
AÇIKLAMA : dinamik bir dizi oluşturduk.
*****************************
CEVAP : C++11 ile geldi.
*****************************Başka bir fikir, capture initialisers potansiyel bir optimizasyon olarak kullanmaktır
Her çağrışımızda bir değer hesaplamak yerine lambda, bunu initialize anında bir kez hesaplayabiliriz:
int main ()
{
using namespace std::string_literals;
const std::vector<std::string> vs = { "apple" , "orange" , "foobar" , "lemon" };
const auto prefix = "le"s;
auto result = std::find_if(vs.begin(), vs.end(), [&prefix](const std::string& sc) {
return sc == prefix + "mon"s; }
);
if (result != vs.end())
{
std::cout << prefix << "-something found!\n";
}
result = std::find_if(vs.begin(), vs.end(), [savedString = prefix + "bar"s](const std::string& sc) {
return sc == savedString; }
);
if (result != vs.end())
{
std::cout << prefix << "-something found!\n";
}
}
*****************************
AÇIKLAMA :
Yukarıdaki kod std::find_if'e yapılan iki çağrıyı gösterir.
İlk senaryoda prefix capture ve input değerini prefix + "mon"s ile karşılaştırın.
Lambda her çağrıldığında, bu dizelerin toplamını saklayan geçici bir değerin yaratılması ve hesaplanması gerekir.
find_if'e yapılan ikinci çağrı bir optimizasyonu gösterir:
dizelerin toplamını hesaplayan, yakalanmış bir saveString değişkeni yaratırız.
Daha sonra buna lambda gövdesinde güvenle başvurabiliriz. Dizelerin toplamı yalnızca bir kez çalıştırılır ve lambda'nın her çağrılmasıyla çalışmaz.
Örnekte ayrıca std::string_literals kullanılmaktadır ve bu nedenle bir std::string nesnesini temsil eden "le" literalleri yazabiliriz.
*****************************
CEVAP :
le-something found!
le-something found!
*****************************stateless lambda fonksiyon adresi türüne örtülü olarak dönüşebilirler.
int main()
{
auto fn = [](int sc){ return sc * 5; };
int(*fp)(int) = fn; // tür dönüştürme operator fonksiyonu sayesinde yazabildik.
std::cout << fp(15);
}int main()
{
int val{};
auto fn = [val](int sc){ return sc * val; };
int(*fp)(int) = fn; // syntax error
}void foo(int (*fp)(int))
{
int val = fp(10);
std::cout << val << "\n";
}
int main()
{
foo([](int sc){ return sc * 5; });
}
*****************************
AÇIKLAMA : örtülü dönüşüm olduğu için çağırabildik.
*****************************
CEVAP : 50
*****************************positive lambda idiom
+[ ] syntax
lambda ifadesinden çıkarım yapıldığında normalde closer type olarak yapılır ancak function pointer olarak yapılmasını istersek kullanırız.
int main()
{
char c = 'a';
c; // L value
+c; // R value ( türü char değil int olur)
}int main()
{
int val{ 10 };
int* ptr = &val;
+ptr;
}
*****************************
AÇIKLAMA : + operatorunun operandı pointer olabilir.
***************************** int foo(int val)
{
return val;
}
int main()
{
int (*fp)(int) = foo;
auto val = +fp;
std::cout << val(5) << "\n";
std::cout << val; // 1
}
*****************************
CEVAP :
5
1
*****************************int main()
{
[](int sc){ return sc * 5; };
}Yukarıdaki lambda ifadesini function pointer ifadesine dönüşürmek istersek :
int main()
{
+[](int sc){ return sc * 5; };
}
*****************************
AÇIKLAMA : + operatorunun operandı yaptığımızda normalde sınıf türünden nesne + operatorunun operandı olamayacağı için derleyici burada sınıfın tür dönüştürme operatorunu çağırması gerekir.
***************************** int main()
{
auto fn = [](int x){ return x * 5; }; // closer type
auto fp = +[](int x){ return x * 5; }; // functor pointer type (int(*f)(int) = +...)
}template <typename T>
void func(T x)
{
if constexpr(std::is_same_v<T, int(*)(int)>)
{
std::cout << "function pointer\n";
}
else
{
std::cout << "closer type\n";
}
}
int main()
{
func([](int x){ return x * 5; });
func(+[](int x){ return x * 5; });
}
*****************************
CEVAP :
closer type
function pointer
*****************************Immediately Invoked Functional Expression
C++ dilinde (IIFE), bir fonksiyon oluşturulduktan hemen sonra tanımlama ve yürütmeye olanak tanıyan bir programlama yapısıdır. JavaScript gibi dillerden ödünç alınan bir tekniktir ve genellikle kodu yerel bir kapsam içinde kapsüllemek ve hemen çalıştırmak için kullanılır.
IIFE, özellikle adlandırma çakışmalarını önlemek veya kaynak temizliğini yönetmek için belirli değişkenlerin kapsamını sınırlamak istediğinizde, genellikle değişkenler için yerel bir kapsam oluşturmak için kullanılır.
int main()
{
auto val {5};
auto fn = [&](int sc){ return sc * val; }(10);
std::cout << fn << "\n";
}int main ()
{
int x = 1 , y = 1;
[&]() noexcept { ++x; ++y; }();
std::cout << x << ", " << y;
}const auto val = []() {
}();// val1 is int
const auto val1 = []() { return 10; }();
// val2 is std::string
const auto val2 = []() -> std::string { return "ABC"; }();int main ()
{
const int val = [&]{
int val = 10;
if (val < 15)
{
val = val * 5;
}
return val;
}();
std::cout << val;
}
*****************************
AÇIKLAMA : bu şekilde yazmasaydık eğer global bir fonksiyon yazmak zorunda kalırdık.
local değişkenlerde kullanılabilsin diye & ile capture ettik.
*****************************
CEVAP : 50
*****************************int main ()
{
const int sc = 10;
auto foo = [sc] () mutable {
std::cout << std::is_const_v<decltype(sc)> << "\n";
sc = 11;
};
foo();
}
*****************************
AÇIKLAMA : bir const değişken capture edilirse constluk korunur.
*****************************
CEVAP : error: assignment of read-only variable ‘sc’
*****************************struct Data {
int m_val;
Data(int val) : m_val([&]) {
// ...
}()) {}
};kodun bir defa çalışmasını istersek.
class Once{
public :
Once()
{
static auto _{ [] {
std::cout << "once worked\n";
return 0;
}() };
}
};
int main ()
{
Once w1;
Once w2;
Once w3;
}
*****************************
CEVAP : once worked
*****************************overload resolution idiom
int func(int) { return 1; }
int func(double){ return 1.5; }
int func(long){ return 1L; }
int main ()
{
std::vector<int> ivec(100, 5);
std::vector<int> ivec1(100);
// std::transform(ivec.begin(), ivec.end(), ivec1.begin(), func); // syntax error
// fonksiyonun adresini static_cast ile istediğimiz fonksiyon adresi türüne dönüştürmek
std::transform(ivec.begin(), ivec.end(), ivec1.begin(), static_cast<int(*)(int)>(func));
// C style ile de yapabiliriz.
std::transform(ivec.begin(), ivec.end(), ivec1.begin(), (int(*)(int))func);
// lambda prefix
std::transform(ivec.begin(), ivec.end(), ivec1.begin(), [](auto val){ return func(val); });
}
*****************************
AÇIKLAMA : transform ilgili rangeteki öğeleri callable a gönderir ve geri dönüş değerini 3. parametreye geçtiğimiz konumdan itibaren yazar.
Doğal olarak fonksiyonun adresi gönderilir.
***************************** C++20 ile gelen unevaluated context içinde lambda ifadelerinin kullanılması ve lambdaların default ctor ve copy assignment fonksiyonlarının eskiden delete edilmiş iken artık edilmemiş olmasıdır.
int main ()
{
auto fn = [](int sc){ return sc * 5; };
decltype(fn) val;
}
*****************************
AÇIKLAMA : c++20 öncesi bu kod illegal idi. (Ancak stateless olması şartıyla)
*****************************
CEVAP : syntax error
error: use of deleted function
*****************************int main ()
{
auto fn = [](int sc){ return sc * 5; };
auto val = fn;
}
*****************************
AÇIKLAMA : Bu hem c++17 hem de c++20 de legal
***************************** int main ()
{
auto fn = [](int sc){ return sc * 5; };
auto val = fn;
fn = val;
}
*****************************
AÇIKLAMA : c++20 öncesi error çünkü copy assignment delete edilmiştir.
*****************************
CEVAP :
error: use of deleted function ‘main()::& main()::::operator=(const main()::&)’
*****************************Önemli olmasının nedenleri :
STL deki bazı sınıfların kullanılmasıyla ilgili
Bazı container sınıflar ve onların template class kodlarında bazı durumlarda template parametresi türünden bir nesnenin default initialize edilmesi gerekir. (default constructor çağırılmalıdır)
Bu durumda lambda ifadesinden elde edilecek bir closer type doğrudan kullanılamaz çünkü default constructor yoktur.
int main ()
{
auto fn = [](int a, int b) {
return abs(a) < abs(b);
};
std::set<int, decltype(fn)> sc;
}
*****************************
AÇIKLAMA : C++20 öncesi error
Arka planda set sınıfının ctor'u comparator türündne bir nesneyi default initialize etmeye çalıştı
ancak neden closer type def init edilemesin ki ?
*****************************
CEVAP :
error: use of deleted function ‘main()::::()’
*****************************int main ()
{
auto fn = [](int a, int b) {
return abs(a) < abs(b);
};
std::set<int, decltype(fn)> sc(fn);
}
*****************************
AÇIKLAMA : C++20 öncesi de legal
***************************** Bir nesne sadece tek bir yerde kullanılacaksa scope boş yere geniş tutulmamalıdır.
int main ()
{
auto fn = [](int a, int b) {
return abs(a) < abs(b);
};
std::set<int, decltype(fn)> sc(fn);
sc.insert({ 4, -5, 7, -3, 5, -2, 9, 1, -1 });
for (auto val : sc)
{
std::cout << val << " " ;
}
}
*****************************
AÇIKLAMA : C++17
*****************************
CEVAP : 1 -2 -3 4 -5 7 9
*****************************int main ()
{
auto fn = [](int a, int b) {
return abs(a) < abs(b);
};
std::set<int, decltype(fn)> sc{ 4, -5, 7, -3, 5, -2, 9, 1, -1 };
for (auto val : sc)
{
std::cout << val << " " ;
}
}
*****************************
AÇIKLAMA : C++20
*****************************
CEVAP : 1 -2 -3 4 -5 7 9
*****************************Sınıfın veri elemanı closer type türünden olacak yani lambda ifadesinden oluşturulacak ise doğrudan yapmanın yolu yoktur.
Ya elimizde o türden bir nesne olmalıdır (decltype(fn) sc;)
default constructor sadece stateless lambdalar için geçerlidir!!!
int main ()
{
int val{};
auto fn = [val]{ return val * 5; };
decltype(fn) sc;
}
*****************************
AÇIKLAMA : C++20'de de syntax error. stateless lambda olmalıdır.
***************************** C++20 ile unevaluated contexte(işlem kodu üretilmeyen bağlam) lambda ifadelerini doğrudan kullanabiliyoruz.
int main ()
{
constexpr auto sz = sizeof([](int sc){ return sc * 5; });
}
*****************************
AÇIKLAMA : c++20 ile legal c++17 de aşağıdaki hata alınır.
*****************************
CEVAP : sizeof operandı olan ifade unevaluated context.
lambda-expression in unevaluated context only available with ‘-std=c++20’ or ‘-std=gnu++20’
*****************************int main ()
{
decltype([](int sc){ return sc * 5; }) fn;
std::cout << fn(15);
}
*****************************
CEVAP : decltype operandı olan ifade unevaluated context.
lambda-expression in unevaluated context only available with ‘-std=c++20’ or ‘-std=gnu++20’
*****************************std::unique_ptr sınıfının 2.template parametresi deleter.
Bazı durumlarda custom deleter kullanılır ve bunun için closer type kullanılmaktadır.
C++17 ve öncesinde closer type default constructor delete edildiği için aşağıdaki gibi yazmamız gerekirdi.
int main ()
{
auto fn = [](int* ptr) {
std::cout << ptr << " adresindeki nesne delete edildi.\n";
delete ptr;
};
std::unique_ptr<int, decltype(fn)> uptr(new int{ 15 }, fn);
}
*****************************
AÇIKLAMA : c++17 ile
***************************** int main ()
{
std::unique_ptr<int, decltype([](int* ptr){
std::cout << ptr << " adresindeki nesne delete edildi.\n";
delete ptr;
})> uptr(new int{ 15 });
}
*****************************
AÇIKLAMA : c++20 ile
***************************** std::set ile örnekleyelim...
template <typename C>
void print(const C& con)
{
for (const auto& elem : con)
{
std::cout << elem << " ";
}
std::cout << "\n";
}
int main ()
{
std::set<std::string> names = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" };
print(names);
using g_set = std::set<std::string, decltype([](const auto& l, const auto& r){ return l > r; })>;
g_set g_names = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" };
print(g_names);
using len_set = std::set<std::string, decltype([](const auto& l, const auto& r){ return l.size() < r.size(); })>;
len_set len_names = { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" };
print(len_names);
std::set<int> iset = { 9, -3, 12, -5, 40, -8, 77, -75 };
print(iset);
using abs_set = std::set<int, decltype([](const auto& l, const auto& r){ return abs(l) < abs(r); })>;
abs_set abs_vals = { 9, -3, 12, -5, 40, -8, 77, -75 };
print(abs_vals);
}
*****************************
AÇIKLAMA : c++20 de legal
*****************************
CEVAP :
Earth Jupiter Mars Mercury Neptune Saturn Uranus Venus
Venus Uranus Saturn Neptune Mercury Mars Jupiter Earth
Mars Venus Saturn Mercury
-75 -8 -5 -3 9 12 40 77
-3 -5 -8 9 12 40 -75 77
*****************************attribute hala doğrudan kullanılamıyor. c++20 ve c++23 standartları dahil...
int main ()
{
auto fn = [](int sc)[[nodiscard]]{ return sc * sc; };
}
*****************************
AÇIKLAMA : c++20 ve 23 içinde geçersiz
*****************************
CEVAP : warning: ‘nodiscard’ attribute can only be applied to functions or to class or enumeration types [-Wattributes]
*****************************familiar template syntax
c++14
generic lambda ifadelerinde template parametresinin doğrudan template sentaksı ile yazılabilmesidir.
[ ] ifadesinden sonra lambda introducer gelirken artık oraya da bişey yazabiliyoruz.
int main ()
{
auto fn = []<typename T>(T t){};
}
*****************************
AÇIKLAMA : c++14
***************************** int main ()
{
auto fn = []<typename T>(T x, T y){ return x + y; };
std::cout << fn(3, 5) << "\n";
}int main ()
{
auto fn = []<typename T>(const std::vector<T>& vec){
return vec.size();
};
std::vector<int> ivec(10);
std::vector<double> dvec(17);
std::cout << fn(ivec) << "\n";
std::cout << fn(dvec);
}
*****************************
CEVAP :
10
17
*****************************int main ()
{
auto fn = []<typename T>(const std::vector<T>& x, const std::vector<T>& y){
// ...
};
std::vector<int> vx;
std::vector<int> vy;
fn(vx, vy);
}int main ()
{
auto fn = []<typename T>(const std::vector<T>& x, const std::vector<T>& y){
// ...
};
std::vector<int> vx;
std::vector<double> vy;
fn(vx, vy);
}
*****************************
AÇIKLAMA : syntax error
***************************** non-type parametre içinde kullanılabilr.
int main ()
{
auto fn = []<class T, int cnt>(T (&tmp)[cnt]) {
for (auto& t : tmp)
{
t += 10;
}
};
int arr[]{ 1, 5, 8 };
fn(arr);
for (auto& a : arr)
{
std::cout << a << " ";
}
}
*****************************
CEVAP :
11 15 18
*****************************perfect forwarding yazmak daha kolay hale geldi.
int main ()
{
auto fn = [](auto&& r) {
foo(std::forward(decltype(r))(r));
};
}int main ()
{
auto fn = []<typename T>(T&& r) {
foo(std::forward<T>(r));
};
}template <typename ...Args>
void foo(Args&& ...args){}
int main ()
{
auto fpush = []<typename T>(std::vector<T>& sc, const T& val) {
sc.push_back(val);
};
auto call_foo = []<typename ...Args>(Args&& ...args) {
foo(std::forward<Args>(args)...);
};
}template <typename T>
void foo(T){}
int main ()
{
auto fn = [](auto sc){ return sc * sc; };
fn(15);
}template <typename T>
void foo(T){}
int main ()
{
auto fn = [](auto x){ return x * x; };
foo(10);
}int main ()
{
auto fn = []<int cnt>(){
int arr[cnt]{};
return arr;
};
fn.operator()<10>();
}
*****************************
AÇIKLAMA : fonksiyondan otomatik ömürlü nesnenin adresi döndürülüyor dikkat edilmeli.
*****************************
CEVAP :
warning: address of local variable ‘arr’ returned
*****************************


Comments