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
is just
std::vector<int>
, notstd::vector<int> const&
. As a result, if we strip out the lambda andstd::function
part of your code, we effectively have:lambda
returns a temporary. It effectively is just copied its input. This temporary is bound the reference return incallback
. But temporaries bound to a reference in areturn
statement do not have their lifetime extended, so the temporary is destroyed at the end of the return statement. Thus, at this point: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:
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]:where, [func.wrap.func]:
where, [func.require], emphasis mine:
So, if we had:
That actually meets all the standard requirements:
INVOKE(func)
is well-formed and while it returnsT
,T
is implicitly convertible toT 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 ifR
is a reference type thenF
must return a reference type as well.If you use:
In your lambda it will work.
This will make your lambda's return type a
std::reference_wrapper<std::vector<int>>
which is implicitly convertible tostd::vector<int>&
.I did a bit of my own searching regarding the
std::function
constructor. It seems this part is an oversight in the interaction ofstd::function
and the standard'sCallable
concept.std::function<R(Args...)>::function<F>(F)
requiresF
to beCallable
asR(Args...)
, which in itself seems reasonable.Callable
forR(Args...)
requiresF
's return type (when given arguments of typesArgs...
to be implicitly convertible toR
, which also in itself seems reasonable. Now whenR
isconst R_ &
, this will an allow implicit conversion ofR_
toconst R_ &
because const references are allowed to bind to rvalues. This is not necessarily unsafe. E.g. consider a functionf()
that returns anint
, but is considered callable asconst int &()
.There is no issue here because of C++'s rules for extending the lifetime of a temporary. However, the following has undefined behavior:
This is because lifetime extension does not apply here. The temporary is created inside of
std::function
'soperator()
and is destroyed before the comparison.Therefore,
std::function
's constructor probably should not rely onCallable
alone, but enforce the additional restriction that implicit conversion of an rvalue to aconst
lvalue in order to bind to a reference is forbidden. Alternatively,Callable
could be changed to never allow this conversion, at the expense of disallowing some safe usage (if only because of lifetime extension).To make things more complicated yet,
fWrapped()
from the above example is perfectly safe to call, as long as you don't access the target of the dangling reference.