Introducidas en C ++ 11, las lambdas se han convertido en una de las características más interesantes del nuevo estándar de lenguaje, lo que hace que el código genérico sea más simple y legible. Cada nueva versión del estándar C ++ agrega nuevas características a las lambdas, lo que hace que el código genérico sea aún más fácil y legible. ¿Notó que la palabra "generalizado" se repitió dos veces? Esto es por una buena razón: las lambdas funcionan muy bien con código basado en plantillas. Pero cuando tratamos de usarlos en un código no genérico y de tipo específico, nos encontramos con una serie de problemas. Un artículo sobre las razones y formas de solucionar estos problemas.
En lugar de presentar
Primero, definamos la terminología: llamamos a lambda expresión lambda, que es una expresión de C ++ que define un objeto de cierre . Aquí hay una cita del estándar C ++:
[expr.prim.lambda.general]
Una expresión-lambda es un prvalue cuyo objeto de resultado se llama objeto de cierre .
[ Nota 1 : un objeto de cierre se comporta como un objeto de función. - nota final ]
Un tipo de objeto de cierre es una clase única y sin nombre.
[expr.prim.lambda.closure]
El tipo de expresión lambda (que también es el tipo del objeto de cierre) es un tipo de clase sin unión, sin nombre, llamado tipo de cierre, cuyas propiedades se describen a continuación.
«» , , , . «» , , . . ( ) :
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?
-
Muchas gracias a Valery Artyukhin por la revisión.