I'm having trouble with std::functions created from lambdas if the function returns a reference but the return type isn't explicitly called out as a reference. It seems that the std::function is created fine with no warnings, but upon calling it, a value is returned when a reference is expected, causing things to blow up. Here's a very contrived example:
#include <iostream>
#include <vector>
#include <functional>
int main(){
std::vector<int> v;
v.push_back(123);
std::function<const std::vector<int>&(const std::vector<int>&)> callback =
[](const std::vector<int> &in){return in;};
std::cout << callback(v).at(0) << std::endl;
return 0;
}
This prints out garbage, however if the lambda is modified to explicitly return a const reference it works fine. I can understand the compiler thinking the lambda is return-by-value without the hint (when I originally ran into this problem, the lambda was directly returning the result from a function that returned a const reference, in which case I would think that the const reference return of the lambda would be deducible, but apparently not.) What I am surprised by is that the compiler lets the std::function be constructed from the lambda with mismatched return types. Is this behavior expected? Am I missing something in the standard that allows this mismatch to occur? I'm seeing this with g++ (GCC) 4.8.2, haven't tried it with anything else.
Thanks!
Why is it broken?
When the return type of a lambda is deduced, reference and cv-qualifications are dropped. So the return type of
[](const std::vector<int> &in){return in;};
is just std::vector<int>
, not std::vector<int> const&
. As a result, if we strip out the lambda and std::function
part of your code, we effectively have:
std::vector<int> lambda(std::vector<int> const& in)
{
return in;
}
std::vector<int> const& callback(std::vector<int> const& in)
{
return lambda(in);
}
lambda
returns a temporary. It effectively is just copied its input. This temporary is bound the reference return in callback
. But temporaries bound to a reference in a return
statement do not have their lifetime extended, so the temporary is destroyed at the end of the return statement. Thus, at this point:
callback(v).at(0)
-----------^
we have a dangling reference to a destroyed copy of v
.
The solution is to explicitly specify the return type of the lambda to be a reference:
[](const std::vector<int> &in)-> const std::vector<int>& {return in;}
[](const std::vector<int> &in)-> decltype(auto) {return in;} // C++14
Now there are no copies, no temporaries, no dangling references, and no undefined behavior.
Who's at fault?
As to whether this is expected behavior, the answer is actually yes. The conditions for constructibility of a std::function
are [func.wrap.func.con]:
f
is Callable (20.9.12.2) for argument typesArgTypes...
and return typeR
.
where, [func.wrap.func]:
A callable object
f
of typeF
is Callable for argument typesArgTypes
and return typeR
if the expressionINVOKE (f, declval<ArgTypes>()..., R)
, considered as an unevaluated operand (Clause 5), is well formed (20.9.2).
where, [func.require], emphasis mine:
Define
INVOKE(f, t1, t2, ..., tN, R)
asstatic_cast<void>(INVOKE (f, t1, t2, ..., tN))
ifR
is cvvoid
, otherwiseINVOKE(f, t1, t2, ..., tN)
implicitly converted toR
.
So, if we had:
T func();
std::function<T const&()> wrapped(func);
That actually meets all the standard requirements: INVOKE(func)
is well-formed and while it returns T
, T
is implicitly convertible to T const&
. So this isn't a gcc or clang bug. This is likely a standard defect, as I don't see why you would ever want to allow such a construction. This will never be valid, so the wording should likely require that if R
is a reference type then F
must return a reference type as well.
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