4

I want to pass a non-capturing lambda, which returns a std::unique_ptr<Derived>, as a function pointer of type std::unique_ptr<Base>(*)().

However, this only works if I explicitly state the return type of the lambda as std::unique_ptr<Base>.

  • Why do I have explicitly state the return type?
  • Why does it work for a std::function without this extra return type?

#include <functional>
#include <memory>

struct Base{virtual ~Base()=default;};
struct Derived : Base{};

struct FailsForF2
{
   using Function = std::add_pointer_t<std::unique_ptr<Base>()>;
   FailsForF2(Function f) {}
};

struct Works
{
   using Function = std::function<std::unique_ptr<Base>()>;
   Works(Function f) {}
};


std::unique_ptr<Derived> fun() {return std::make_unique<Derived>();} 

int main()
{

   auto f1 = [](){return std::make_unique<Base>();};
   auto f2 = [](){return std::make_unique<Derived>();};
   auto f3 = []()->std::unique_ptr<Base>{return std::make_unique<Derived>();};

   Works x1(f1);
   Works x2(f2);
   Works x3(f3);

   FailsForF2 x4(f1);
   FailsForF2 x5(f2);
   FailsForF2 x6(f3);
}

gcc error:

main.cpp: In function 'int main()':

main.cpp:34:20: error: invalid user-defined conversion from 'main()::<lambda()>' to 'FailsForF2::Function {aka std::unique_ptr<Base> (*)()}' [-fpermissive]

    FailsForF2 x5(f2);

                    ^

main.cpp:26:17: note: candidate is: main()::<lambda()>::operator std::_MakeUniq<Derived>::__single_object (*)()() const <near match>

    auto f2 = [](){return std::make_unique<Derived>();};

                 ^

main.cpp:26:17: note:   no known conversion from 'std::_MakeUniq<Derived>::__single_object (*)() {aka std::unique_ptr<Derived> (*)()}' to 'FailsForF2::Function {aka std::unique_ptr<Base> (*)()}'

main.cpp:10:4: note:   initializing argument 1 of 'FailsForF2::FailsForF2(FailsForF2::Function)'

    FailsForF2(Function f) {}

live example

3
  • wouldn't std::add_pointer_t<std::unique_ptr<Base>>; be a pointer to a unique_ptr<base>? See coliru.stacked-crooked.com/a/5a1c461bfb6199a8 I may be no expert here but I can guess the compiler has a problem converting a unique_ptr<derrived> to a pointer to a unique_ptr<base> while converting a unique_ptr<base> to a pointer to unique_ptr<base> it can somehow do implicitly Commented Aug 25, 2016 at 13:56
  • @Hayt I use std::add_pointer_t<std::unique_ptr<Base>()>; please note the ( ) at the end Commented Aug 25, 2016 at 13:57
  • Ah yeah right. Nevermind that then.It maybe has to do something with the different behavior of a function ptr (which the first is) and a function object with some conversion but I could not find any links to that right now (in combinations to lambdas. There may be somethings with converison of a lambda to a c-function ptr also). Commented Aug 25, 2016 at 14:01

2 Answers 2

5

TL;DR;

  • FailsForF2 fails because std::unique_ptr<Derived> (*) () is not implicitly convertible to std::unique_ptr<Base> (*) ();
  • Works works because std::unique_ptr<Derived> is implicitly convertible to std::unique_ptr<Base> (see the standard quotes at the end).

A lambda is implicitly convertible to a function pointer with the same return type and arguments1, so your three lambdas are respectively convertible to:

std::unique_ptr<Base> (*) ()
std::unique_ptr<Derived> (*) ()
std::unique_ptr<Base> (*) ()

Since std::unique_ptr<Derived> (*) () is different from (and not convertible to) std::unique_ptr<Base> (*) (), there is no viable overload for FailsForF2 constructor. See with the following piece of code:

std::unique_ptr<Derived> (*pfd) () = f2; // Compiles.
std::unique_ptr<Base> (*pfb) () = pfd;   // Does not compile (invalid conversion).

When you explicitly specify the return type of the lambda, you change the return type of the call operator of the lambda (the closure type associated to it actually, see the quote at the end), so the conversion is possible.


std::function on the other hand does not have such constraints - The constructor of std::function is templated so it can take any callable:

template <typename F>
std::function(F &&f);

...as long as the following is valid2:

INVOKE(f, std::forward<Args>(args)..., R)

1 Standard quote from N4594, §5.1.5/7 (emphasis is mine):

The closure type for a non-generic lambda-expression with no lambda-capture has a conversion function to pointer to function with C++ language linkage (7.5) having the same parameter and return types as the closure type’s function call operator. [...]

2 Standard quote from N4594, §20.12.12.2/2:

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE (f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.12.2).

...and §20.12.2 (emphasis is mine, 1.1 through 1.6 are about pointer (or alike) to member functions, so not relevant here):

1 Define INVOKE (f, t1, t2, ..., tN) as follows:

(1.x) - [...]

(1.7) - f(t1, t2, ..., tN) in all other cases.

2 Define INVOKE (f, t1, t2, ..., tN, R) as static_cast(INVOKE (f, t1, t2, ..., tN)) if R is cv void, otherwise INVOKE (f, t1, t2, ..., tN) implicitly converted to R.

Sign up to request clarification or add additional context in comments.

Comments

1

In addition to Holt's answer, and to cover your first question: You don't necessarily have to specify the return type explicitly as a trailing return type. But since you're creating a unique_ptr<Derived>, but want a unique_ptr<Base>, you should return the latter and perform the conversion within your function.

So, I believe, that's something along the lines of

auto f2 = [](){ return std::unique_ptr<Base>{new Derived()}; };

which also compiles.

1 Comment

@m.s. There is no exception safety issue here (but I agree that in general you should go for make_unique), but if you need you could std::unique_ptr<Base>(std::make_unique<Derived>()) (which is basically what f3 does implicitly, or the std::function version) - But if you need the conversion to function pointer, the best way is to explicitly specify the return type as in f3.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.