Diperkenalkan di C ++ 11, lambda telah menjadi salah satu fitur paling keren dari standar bahasa baru, membuat kode generik lebih sederhana dan lebih mudah dibaca. Setiap versi baru dari standar C ++ menambahkan fitur baru ke lambda, membuat kode generik lebih mudah dan lebih mudah dibaca. Apakah Anda memperhatikan bahwa kata "umum" diulangi dua kali? Ini untuk alasan yang bagus - lambda bekerja sangat baik dengan kode berbasis template. Tetapi ketika kami mencoba menggunakannya dalam kode non-generik, kode khusus jenis, kami mengalami sejumlah masalah. Artikel tentang alasan dan cara memecahkan masalah ini.
Alih-alih memperkenalkan
Pertama, mari kita definisikan terminologi: kita memanggil lambda sebagai ekspresi lambda, yang merupakan ekspresi C ++ yang mendefinisikan objek penutupan . Berikut kutipan dari standar C ++:
[expr.prim.lambda.general]
Sebuah lambda ekspresi adalah prvalue yang objek hasilnya disebut objek penutupan .
[ Catatan 1 : Objek penutupan berperilaku seperti objek fungsi. - catatan akhir ]
Tipe objek closure adalah kelas yang unik dan tidak bernama.
[expr.prim.lambda.closure]
Tipe ekspresi lambda (yang juga merupakan tipe objek closure) adalah tipe kelas non-union unik dan tidak bernama, yang disebut tipe closure, yang propertinya dijelaskan di bawah ini.
«» , , , . «» , , . . ( ) :
auto l1 = [](int x) { return x; };
auto l2 = [](int x) { return x; };
static_assert(!std::is_same_v<decltype(l1), decltype(l2)>);
, :
template <typename Func>
class LambdaDependent {
public:
explicit LambdaDependent(Func f) : f_{f} {}
private:
Func f_;
};
LambdaDependent ld1{l1};
LambdaDependent ld2{l2};
static_assert(!std::is_same_v<decltype(ld1), decltype(ld2)>);
, , (, std::vector<>).
std::function<>. , std::function<> :
std::function f1{l1};
std::function f2{l2};
static_assert(std::is_same_v<decltype(f1), decltype(f2)>);
, . std::function<> , , , - legacy API. , :
int api_func(int(*fp)(int), int value) {
return fp(value);
}
, (l1 l2), :
std::cout << api_func(l1, 123) << '\n'; // 123
std::cout << api_func(l2, 234) << '\n'; // 234
, ( ) :
[expr.prim.lambda.closure]
The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator. The conversion is to βpointer to noexcept functionβ if the function call operator has a non-throwing exception specification. The value returned by this conversion function is the address of a function F that, when invoked, has the same effect as invoking the closure type's function call operator on a default-constructed instance of the closure type. F is a constexpr function if the function call operator is a constexpr function and is an immediate function if the function call operator is an immediate function.
static_cast<>:
LambdaDependent lf1{static_cast<int(*)(int)>(l1)};
LambdaDependent lf2{static_cast<int(*)(int)>(l2)};
static_assert(std::is_same_v<decltype(lf1), decltype(lf2)>);
, static_cast<> :
LambdaDependent ls1{+l1};
LambdaDependent ls2{+l2};
static_assert(std::is_same_v<decltype(ls1), decltype(ls2)>);
- , + .
[over.built]
For every type T there exist candidate operator functions of the form
T* operator+(T*);
, , , static_cast<>.
, , ? C β void*. , .
int api_func_ctx(int(*fp)(void*, int), void* ctx, int value) {
return fp(ctx, value);
}
:
int counter = 1;
auto const_lambda = [counter](int value) {
return value + counter;
};
std::cout << api_func_ctx([](void* ctx, int value) {
auto* lambda_ptr = static_cast<decltype(const_lambda)*>(ctx);
return (*lambda_ptr)(value);
}, &const_lambda, 123) << '\n'; // 124
, , . void*, , . , mutable :
auto mutable_lambda = [&counter](int value) mutable {
++counter;
return value * counter;
};
std::cout << api_func_ctx([](void* ctx, int value) {
auto* lambda_ptr = static_cast<decltype(mutable_lambda)*>(ctx);
return (*lambda_ptr)(value);
}, &mutable_lambda, 123) << ':' << counter << '\n'; // 246:2
, . api_func_ctx , .
, , , 2 :
void* ( type erasure);
, .
«» closure_erasure:
template <typename Ret, typename ...Args>
struct closure_erasure {
Ret(*func)(void*, Args...);
void* ctx;
};
: ? CTAD β . , ? operator(), . :
template<typename Lambda>
explicit closure_erasure(Ret(Lambda::*)(Args...), void* ctx) :
func{
[](void* c, Args ...args) {
auto* lambda_ptr = static_cast<Lambda*>(c);
return (*lambda_ptr)(std::forward<Args>(args)...);
}
},
ctx{ctx} {}
template<typename Lambda>
explicit closure_erasure(Ret(Lambda::*)(Args...) const, void* ctx) :
func{
[](void* c, Args ...args) {
auto* lambda_ptr = static_cast<Lambda*>(c);
return (*lambda_ptr)(std::forward<Args>(args)...);
}
},
ctx{ctx} {}
const β , (), mutable .
: , operator() :
auto make_closure_erasure = [](auto& lmb) {
return closure_erasure{
&std::remove_reference_t<decltype(lmb)>::operator(), &lmb};
};
, . , : !
, noexcept, :
template <typename Ret, bool NoExcept, typename ...Args>
struct closure_erasure {
Ret(*func)(void*, Args...) noexcept(NoExcept);
void* ctx;
template<typename Lambda>
explicit closure_erasure(Ret(Lambda::*)(Args...) noexcept(NoExcept), void* ctx) :
func{
[](void* c, Args ...args) noexcept(NoExcept) {
auto* lambda_ptr = static_cast<Lambda*>(c);
return (*lambda_ptr)(std::forward<Args>(args)...);
}
},
ctx{ctx} {}
template<typename Lambda>
explicit closure_erasure(Ret(Lambda::*)(Args...) const noexcept(NoExcept), void* ctx) :
func{
[](void* c, Args ...args) noexcept(NoExcept) {
auto* lambda_ptr = static_cast<Lambda*>(c);
return (*lambda_ptr)(std::forward<Args>(args)...);
}
},
ctx{ctx} {}
};
auto make_closure_erasure = [](auto& lmb) {
return closure_erasure{
&std::remove_reference_t<decltype(lmb)>::operator(), &lmb};
};
auto li = make_closure_erasure(const_lambda);
std::cout << api_func_ctx(li.func, li.ctx, 123) << '\n'; // 124
li = make_closure_erasure(mutable_lambda);
std::cout << counter << ':' <<
api_func_ctx(li.func, li.ctx, 123) << '\n'; // 2:369
std::cout << counter << ':' <<
api_func_ctx(li.func, li.ctx, 123) << '\n'; // 3:492
-
-
Back to Basics: Lambdas from Scratch - Arthur O'Dwyer - CppCon 2019
C++ Weekly - Ep 246 - (+[](){})() What Does It Mean?
-
Terima kasih banyak kepada Valery Artyukhin untuk mengoreksi bacaan .