In C++14, why do lambda functions with a deduced return type drop references from the return type by default? IIUC, since C++14 lambda functions with a deduced return type (without an explicit trailing return type) have a return type of auto
, which drops references (among other things).
Why was this decision made? It seems to me like a gotcha to remove a reference when that's what your return statement returns.
This behavior caused the following nasty bug 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
resolves the issue:
D d{ [&c]() -> const Int& { return c.getObj(); } }
Interestingly, even if the auto
return type deduction makes sense, isn't it a bug that std::function<const Int&>
gets happily initialized with a function that returns a non-reference? I see this also by writing explicitly:
D d{ [&c]() -> Int { return c.getObj(); } }
which compiles without a problem. (on Xcode 8
, clang 8.0.0
)
I think the place you are stumbling is actually with the expression
c.getObj()
in the linereturn c.getObj();
.You think the expression
c.getObj()
has typeconst Int&
. However that is not true; expressions never have reference type. As noted by Kerrek SB in comments, we sometimes talk about expressions as if they had reference type, as a shortcut to save on verbosity, but that leads to misconceptions so I think it is important to understand what is really going on.The use of a reference type in a declaration (including as a return type as in
getObj
's declaration) affects how the thing being declared is initialized, but once it is initialized, there is no longer any evidence that it was originally a reference.Here is a simpler example:
versus
These two codes are exactly identical (except for the result of
decltype(a)
ordecltype(b)
which is a bit of a hack to the system). In both cases the expressionsa
andb
both have typeint
and value category "lvalue" and denote the same object. It's not the case thata
is the "real object" andb
is some sort of disguised pointer toa
. They are both on equal footing. It's one object with two names.Going back to your code now: the expression
c.getObj()
has exactly the same behaviour asc.m_obj
, apart from access rights. The type isInt
and the value category is "lvalue". The&
in the return type ofgetObj()
only dictates that this is an lvalue and it will also designate an object that already existed (approximately speaking).So the deduced return type from
return c.getObj();
is the same as it would be forreturn c.m_obj;
, which -- to be compatible with template type deduction, as mentioned elsewhere -- is not a reference type.NB. If you understood this post you will also understand why I don't like the pedagogy of "references" being taught as "disguised pointers that auto dereference", which is somewhere between wrong and dangerous.