Introduzido no C ++ 11, lambdas se tornou um dos recursos mais interessantes do novo padrão de linguagem, tornando o código genérico mais simples e legível. Cada nova versão do padrão C ++ adiciona novos recursos aos lambdas, tornando o código genérico ainda mais fácil e legível. Você notou que a palavra "generalizado" foi repetida duas vezes? Isso é por um bom motivo - lambdas funcionam muito bem com código baseado em modelo. Mas quando tentamos usá-los em código não genérico específico de tipo, encontramos vários problemas. Um artigo sobre as razões e formas de resolver esses problemas.
Em vez de apresentar
Primeiro, vamos definir a terminologia: chamamos lambda de expressão lambda, que é uma expressão C ++ que define um objeto de encerramento . Aqui está uma citação do padrão C ++:
[expr.prim.lambda.general]
Uma expressão lambda é um prvalue cujo objeto de resultado é chamado de objeto de fechamento .
[ Nota 1 : Um objeto de fechamento se comporta como um objeto de função. - nota final ]
Um tipo de objeto de encerramento é uma classe única sem nome.
[expr.prim.lambda.closure]
O tipo de uma expressão lambda (que também é o tipo do objeto de fechamento) é um tipo de classe sem união sem nome exclusivo, chamado de tipo de fechamento, cujas propriedades são descritas abaixo.
«» , , , . «» , , . . ( ) :
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?
-
Muito obrigado a Valery Artyukhin pela revisão.