In the code snipped below, the compiler silently casts the return-by-copy function pointer into a return-by-const-reference std::function. When the std::function instance is called, a reference to the copy is returned and the application crashes (most of the time ;).
By the way of comparison, ordinary function pointers don't allow this implicit cast, so I wonder if I should go off complaining to the compiler vendor (gcc 4.8 in this case), or is this behaviour mandated by the standard?
#include <iostream>
#include <functional>
typedef std::function<const std::string&(const std::string& x)> F;
std::string bad(const std::string& x) { return x; }
const std::string& good(const std::string& x) { return x; }
typedef const std::string& (*FP)(const std::string&);
int main(int, char**) {
std::cout << F(&good)("hello") << std::endl;
std::cout << F(&bad)("hello") << std::endl;
FP a = &good;
// FP b = &bad; Not allowed!
return 0;
}
P.S. This is a simplified version of a real world problem, where bad
was actually a lambda returning a member of some type:
typedef std::function<const std::string&(const X& x)> F;
F f = [](const X& x) { return x->member(); };
It took us a while to figure out that the return type of this lambda was deduced to std::string
, not const std::string&
, and that this was causing a crash.
This looks like a kind of corner case. The constructor definition in §2.8.11.2.1/7 says:
Requires:
F
shall beCopyConstructible
.f
shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. [...]
§2.8.11.2/2 says:
A callable object
f
of typeF
is Callable for argument typesArgTypes
and return typeR
if the expres- sionINVOKE (f, declval<ArgTypes>()..., R)
, considered as an unevaluated operand (Clause 5), is well formed (20.8.2).
and last §20.8.2/2 says:
Define INVOKE (f, t1, t2, ..., tN, R) as INVOKE (f, t1, t2, ..., tN) implicitly converted to R.
Obviously T
implicitly converts to T const &
and so in absence of further restriction, the constructor should be allowed.
However calling such function involves taking returning reference to a temporary whose life ends before the reference is even returned, which is Undefined Behaviour. And when something is Undefined Behaviour, the implementation may do whatever it pleases. Unfortunately the undefined behaviour only happens at invoking, so it's still not strictly conforming to detect it at construction time.
Because calling it is the only use of the object, it would be better, if it was prohibited. So this should be considered defect in the specification.
In any case, I'd recommend bringing it up on appropriate gcc mailing list. The maintainers be willing to diverge from the specification slightly in case like this or at least they could raise or help you raise the issue with the C++ committee as they work with it regularly.
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