What is the usage of adding -> auto
in []() -> auto { return 4; }
?
For me - it is not different than []() { return 4; }
The return type of a lambda expression is automatically deduced. You don't have to use the auto keyword unless you specify a trailing-return-type. The trailing-return-type resembles the return-type part of an ordinary function or member function.
The return type for a lambda is specified using a C++ feature named 'trailing return type'. This specification is optional. Without the trailing return type, the return type of the underlying function is effectively 'auto', and it is deduced from the type of the expressions in the body's return statements.
The trailing return type feature removes a C++ limitation where the return type of a function template cannot be generalized if the return type depends on the types of the function arguments.
C++ Lambda expression allows us to define anonymous function objects (functors) which can either be used inline or passed as an argument. Lambda expression was introduced in C++11 for creating anonymous functors in a more convenient and concise way.
It is auto
by default. The Standard, [expr.prim.lambda]/4, reads:
If a lambda-expression does not include a lambda-declarator, it is as if the lambda-declarator were
()
. The lambda return type isauto
, which is replaced by the trailing-return-type if provided and/or deduced fromreturn
statements as described in [dcl.spec.auto].
My addition.
So, -> auto
itself is not useful. However, we can form other return types with auto
, namely: -> auto&
, -> const auto&
, -> auto&&
, -> decltype(auto)
. Standard rules of return type deduction apply. One should bear in mind that auto
is never deduced to be a reference type, so by default a lambda returns a non-reference type.
A few (trivial) examples:
// 1.
int foo(int);
int& foo(char);
int x;
auto lambda1 = [](auto& x) { return x; };
static_assert(std::is_same_v<decltype(lambda1(x)), int>);
auto lambda2 = [](auto& x) -> auto& { return x; };
static_assert(std::is_same_v<decltype(lambda2(x)), int&>);
// 2.
auto lambda3 = [](auto x) { return foo(x); };
static_assert(std::is_same_v<decltype(lambda3(1)), int>);
static_assert(std::is_same_v<decltype(lambda3('a')), int>);
auto lambda4 = [](auto x) -> decltype(auto) { return foo(x); };
static_assert(std::is_same_v<decltype(lambda4(1)), int>);
static_assert(std::is_same_v<decltype(lambda4('a')), int&>);
// 3.
auto lambda5 = [](auto&& x) -> auto&& { return std::forward<decltype(x)>(x); };
static_assert(std::is_same_v<decltype(lambda5(x)), int&>);
static_assert(std::is_same_v<decltype(lambda5(foo(1))), int&&>);
static_assert(std::is_same_v<decltype(lambda5(foo('a'))), int&>);
PiotrNycz's addition. As pointed out in comments (credit to @StoryTeller) - the real usage is version with auto&
and const auto&
and "The degenerate case is just not something worth bending backwards to disallow."
See:
int p = 7;
auto p_cr = [&]() -> const auto& { return p; };
auto p_r = [&]() -> auto& { return p; };
auto p_v = [&]() { return p; };
const auto& p_cr1 = p_v(); // const ref to copy of p
const auto& p_cr2 = p_cr(); // const ref to p
p_r() = 9; // we change p here
std::cout << p_cr1 << "!=" << p_cr2 << "!\n";
// print 7 != 9 !
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With