Reputation: 8401
Given a lambda:
auto f = [](const T& var){ return var; };
Why return type of f
is T
(not const T&
)? Where is this in the Standard?
Upvotes: 5
Views: 348
Reputation: 27133
This is a much more fundamental issue about C++. Nothing to do with specifically with lambdas or auto
.
In C++, a reference behaves the same as a non-reference in almost all situations. This is deliberate, and is based on the idea that a reference to an object really should be equivalent to the object itself. In the following code, there is no real difference between x
and y
:
int x = 3;
int &y = x;
In fact, it is impossible to distinguish them (except via decltype
). The are both lvalues of type int
. If you call foo(x)
and foo(y)
, the compiler will treat them both as having the same type and value category and therefore the same overload is selected.
I would interpret x
and y
by saying that both of them are references to the same object. They are two different 'names' for the same object.
Therefore return x
and return y
are equivalent to each other, and therefore the lambda won't care about the &
when deducing its return type.
This explains why the &
is ignored. C++ tries to "ignore" the &
as much as possible, precisely in order that references can be treated as fully equivalent to the original object.
Now that it is established that we are returning by value, not by reference, then we can understand why the const
is also ignored. A copy of an object doesn't need to be const
. Consider this in reverse also: we can pass a const int
argument as an int
parameter of a function.
Upvotes: 4
Reputation: 172924
The point is:
var
) are ignored.Quotes from the standard:
About auto:
If the placeholder is the auto type-specifier, the deduced type T' replacing T is determined using the rules for template argument deduction. Obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initialization is copy-list-initialization, with std::initializer_list. Deduce a value for U using the rules of template argument deduction from a function call ([temp.deduct.call]), where P is a function template parameter type and the corresponding argument is e. If the deduction fails, the declaration is ill-formed. Otherwise, T' is obtained by substituting the deduced U into P.
About the rule of template argument deduction from a function call:
If P is not a reference type:
- If A is a cv-qualified type, the top-level cv-qualifiers of A's type are ignored for type deduction.
About reference:
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
So for var
(i.e. A
) is const T&
, the deduced return type would be T
here.
Upvotes: 5
Reputation: 275385
Return type deduction of auto
return functions with no trailing return type in C++11 or 14 is done as if you did
auto retval = /* return expression */
and auto
always deduces a value type.
Lambdas are the only auto
return functions in C++11. In C++14 it was extended to other functions (following similar rules).
In C++11 you can fix this with a ->decltype(var)
or a ->int const&
trailing return type on your lambda.
In C++14 you can simply ->decltype(auto)
, which changes the return type deduction rules from
auto retval = /* return expression */
to be more like
decltype(/* return expression */) retval = /* return expression */
which, in your case, means not dropping the &
or the const
.
Note that returning const&
parameters is exceedingly dangerous, as rvalues can be turned into const&
implicitly, and reference lifetime extension does not commute past a function call. So:
auto&& x = some_function();
// or
int const& x = some_function();
is safe even if some_function()
returns a temporary, while assuming f
behaves the way you want f
to behave:
auto&& x = f( some_function() );
// or
int const& x = f( some_function() );
generates x
a dangling reference to the temporary returned by some_function()
.
This can break things surprisingly, like the fact that for(:)
loops implicitly use auto&&
parameters behind the scene, and clean up temporaries between the initialization of the range expression and the execution of the iteration.
This answer is not yet complete, as I have not added references into the standard.
Upvotes: 2