Why do lambda functions by default output a reference to the return type of the return value?

In C ++ 14, why do lambda functions with inferred return type return links from the return type by default? IIUC, since C ++ 14 lambda functions with an inferred return type (without an explicit return return type) have an auto return type, which, in particular, replaces references.

Why was this decision made? It seems to me that getcha removes the link when the returned statement is returned.

This behavior caused the following unpleasant errors for me:

 class Int { public: Int(int i) : m_int{i} {} int m_int; }; class C { public: C(Int obj) : m_obj{obj} {} const auto& getObj() { return m_obj; } Int m_obj; }; class D { public: D(std::function<const Int&()> f) : m_f{f} {} std::function<const Int&()> m_f; }; Int myint{5}; C c{myint}; D d{ [&c](){ return c.getObj(); } } // The deduced return type of the lambda is Int (with no reference) const Int& myref = d.m_f(); // Instead of referencing myint, myref is a dangling reference; d.m_f() returned a copy of myint, which is subsequently destroyed. 

Specifying the desired return type when initializing d fixes the problem:

 D d{ [&c]() -> const Int& { return c.getObj(); } } 

Interestingly, even if deduction of type auto makes sense, is it not an error that std::function<const Int&> gets a happy initialization using a function that returns a link without a link? I also see this by writing explicitly:

 D d{ [&c]() -> Int { return c.getObj(); } } 

which compiles without problems. (on Xcode 8 , clang 8.0.0 )

+2
c ++ lambda clang c ++ 14 auto
Jan 08 '17 at 2:56
source share
2 answers

I think the place where you stumble is actually the expression c.getObj() in the string return c.getObj(); .

You think the expression c.getObj() is of type const Int& . However, it is not; expressions never have a reference type. As Kerrek SB notes in the comments, we sometimes talk about expressions, as if they had a reference type, like a shortcut to save on verbosity, but this leads to misconceptions, so I think it’s important to understand what is actually happening.

The use of the reference type in the declaration (including the type of the return value, as in the getObj ) affects how the declared thing is initialized, but after its initialization there is no evidence that it was originally a reference.

Here is a simpler example:

 int a; int &b = a; // 1 

against

 int b; int &a = b; // 2 

These two codes are exactly identical (except for the result of decltype(a) or decltype(b) , which is a bit hacked for the system) . In both cases, the expressions a and b are of type int and the category of values ​​is "lvalue" and denote the same object. This is not the case when a is a “real object” and b is some kind of masked pointer to a . They are both equal. This is a single object with two names.

Returning to your code now: the expression c.getObj() has exactly the same behavior as c.m_obj , except for access rights. The type is int , and the category of values ​​is "lvalue". & in the return type, getObj() only indicates that it is an lvalue value, and it will also indicate an object that already exists (roughly speaking).

Thus, the deduced return type from return c.getObj(); will be the same as for return c.m_obj; , which, for compatibility with template type inference, as mentioned above, is not a reference type.

NB. If you understand this message, you will also understand why I don’t like that the pedagogy of “links” is taught as “masked pointers that automatically play out,” which is somewhere between the wrong and the dangerous.

+3
Jan 08 '17 at 21:30
source share

the standard (at least a working draft) already gives you hints about what is happening and how to solve it:

The return type is the lambda auto , which is replaced by the type indicated by the trailing-return-type if it is specified and / or deduced from the return statements, as described in [dcl.spec.auto]. [Example:

  auto x1 = [](int i){ return i; }; // OK: return type is int auto x2 = []{ return { 1, 2 }; }; // error: deducing return type from braced-init-list int j; auto x3 = []()->auto&& { return j; }; // OK: return type is int& 

- end of example]

Now consider the following template function:

 template<typename T> void f(T t) {} // .... int x = 42; f(x); 

What is t in f , a copy of x or a link to it?
What happens if we change the function as it should?

 template<typename T> void f(T &t) {} 

The same applies to the inferred lambda return type more or less: if you want a link, you should be frank about it.

Why was this decision made? It seems to me that getcha removes the link when the returned statement is returned.

The choice is consistent with how the templates work from the start.
Instead, I would be surprised at the opposite. The type of the return value is displayed, as well as the template parameter, and a pretty good decision not to define different rules for them (at least from my point of view).




However, to solve your problem, you have several alternatives:

  •  [&c]()->auto&&{ return c.getObj(); } 
  •  [&c]()->auto&{ return c.getObj(); } 
  •  [&c]()->decltype(c.getObj())&{ return c.getObj(); } 
  •  [&c]()->decltype(c.getObj())&&{ return c.getObj(); } 
  •  [&c]()->decltype(auto){ return c.getObj(); } 
  •  [&c]()->const Int &{ return c.getObj(); } 
  •  ... 

Some of them are crazy, some of them are completely clear, they should all work.
If the intended behavior is to return the link, perhaps to be explicit, this is the best choice:

 [&c]()->auto&{ return c.getObj(); } 

In any case, it is mostly opinion-based, so feel free to choose your preferred alternative and use it.




Interestingly, even if the output of type auto return makes sense, is it not an error that std :: function is successfully launched using a function that returns a link without a link?

Consider the code below (there is no reason to call std::function right now):

 int f() { return 0; } const int & g() { return f(); } int main() { const int &x = g(); } 

It gives you some warnings, but compiles.
The reason is that the temporary is created from rvalue, and the temporary can be associated with a constant reference, so I would say that this is legal from the point of view of the standard.
The fact that it explodes at runtime is another problem.

Something similar happens when using std:: function .
In any case, this is an abstraction over the common object being called, so don't expect the same warnings.

+3
Jan 08 '17 at 20:41
source share



All Articles