12
votes

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.

1
Does x->member() return std::string or const std::string&?Stas
const std::string&, but for the purpose of return type deduction, it doesn't really matter if it's reference or copy - it will be deduced as copy (the decayed type) (see akrzemi1.wordpress.com/2012/03/27/gotchas-of-type-inference)Jacek Sieka
Did you name the types the wrong way around in that final paragraph? Or have I misunderstood the question?Lightness Races in Orbit
Should have included that F was slightly different in the real world - better now I hope ;)Jacek Sieka

1 Answers

6
votes

This looks like a kind of corner case. The constructor definition in §2.8.11.2.1/7 says:

Requires: F shall be CopyConstructible. 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 type F is Callable for argument types ArgTypes and return type R if the expres- sion INVOKE (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.