vladon
vladon

Reputation: 8401

Why lambda removes cv and ref?

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

Answers (3)

Aaron McDaid
Aaron McDaid

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

songyuanyao
songyuanyao

Reputation: 172924

The point is:

  1. The use of auto for return type deduction employ template type deduction rule.
  2. The return type is delcared as passed-by-value; which means that the reference-ness and top-level cv-qualifiers of expression used for deduction (i.e. 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

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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

Related Questions