Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Using lambda for implicit constructor call expecting a function pointer

I am trying to implicitly construct an object from a lambda function. The constructor of the object takes a function pointer as parameter. But the code [ 1 ] doesn't compile with the message:

6 : <source>:6:5: note: candidate constructor not viable: no known conversion from '(lambda at /tmp/compiler-explorer-compiler117117-54-dfxyju.lkw98/example.cpp:22:14)' to 'Bar' (aka 'bool (*)()') for 1st argument
    Foo(Bar b) : m_b{b} {}

But the standard states that a lambda function is implicitly convertible to a function pointer with the same parameter and return type [ 2 ]. This should be applicable here and therefore I would expect the constructor to be callable.

So why doesn't the code compile? Thanks for your explanations!


[ 1 ] Code example:

using Bar = bool(*)();

class Foo
{
public:
    Foo(Bar b) : m_b{b} {}
private:
    Bar m_b;
};

int main()
{   
    // working
    Foo f1 ( [](){ return true; });
    Foo f2 = Bar( [](){ return true; });

    // working implicit conversion
    bool(*tmp)() = []() { return true; };
    Foo f3 = tmp;

    // not working
    Foo f4 = [](){ return true; };

    return 0;
}

https://godbolt.org/g/QE4v1Z


[ 2 ] The C++14 Standard states in section 5.1.2 that:

The closure type for a lambda-expression with no lambda-capture has a public non-virtual non-explicit const conversion function to pointer to function having the same parameter and return types as the closure type’s function call operator. The value returned by this conversion function shall be the address of a function that, when invoked, has the same effect as invoking the closure type’s function call operator.

Which means the lambda should be implicitly (non-explicit) convertable to the function pointer.


tested with:

  • Clang5.0.0 -std=c++14
  • MSVC14.12 /permissive-
like image 917
DrLudwig3 Avatar asked Dec 07 '17 14:12

DrLudwig3


1 Answers

Yes it is implicitly convertible to the function pointer; that's why bool(*tmp)() = []() { return true; }; works. The point is that in one implicit conversion sequence, only one user-defined conversion is allowed.

Implicit conversion sequence consists of the following, in this order:

  1. zero or one standard conversion sequence;
  2. zero or one user-defined conversion;
  3. zero or one standard conversion sequence.

When considering the argument to a constructor or to a user-defined conversion function, only one standard conversion sequence is allowed (otherwise user-defined conversions could be effectively chained).

For Foo f4 = [](){ return true; };, which is copy initialization, the lambda has to be converted to the function pointer via the user-defined conversion function of lambda, then converted to Foo via the converting constructor of Foo, two user-defined conversions are required, but not allowed.

BTW:

  1. Foo f1 ( [](){ return true; }); works because for direct intialization the constructor of Foo will be invoked directly; the lambda is converted to the function pointer and then passed as the argument to the constructor, this is fine.

  2. Foo f2 = Bar( [](){ return true; }); works because lambda is explicitly converted to function pointer, which is converted to Foo implicitly later.

  3. bool(*tmp)() = []() { return true; }; Foo f3 = tmp; works because the lambda is implicitly converted to function pointer as tmp, then tmp is converted Foo; only one user-defined conversion is required for either implicit conversion sequence, then it's fine.

  4. Foo f5 = +[](){ return true; }; works because operator+ causes lambda converted to function pointer, that means for +[](){ return true; } you'll get the function pointer with type bool(*)(), then the story is same as f2.

like image 151
songyuanyao Avatar answered Sep 26 '22 20:09

songyuanyao