Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does std::function allow an implicit cast from reference to copy in its return type?

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.

like image 747
Jacek Sieka Avatar asked Apr 25 '14 05:04

Jacek Sieka


1 Answers

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.

like image 173
Jan Hudec Avatar answered Sep 19 '22 01:09

Jan Hudec