Search code examples
c++c++14function-pointersstd-functiontype-erasure

Why can't you initialize objects with the assignment init but can with the parentheses init when taking std::function in the constructor?


Let's say you have a class taking callable in its constructor.

class SomeClass {
public:
    SomeClass(const std::function<int(void)>& callable)
    {
        ...
    }
};

and a free function

int some_function() { return 0; }

Why is it that you can construct an instance of this class using the parentheses initializer, but not when using the assignment initializer? This goes both for function pointers and the functions themselves.

SomeClass object_one(some_function); // Works.
SomeClass object_two = some_function; // Error.
SomeClass object_three(&some_function); // Works.
SomeClass object_four = &some_function; // Error.

I am using MSVC C++14 and the error message I get is

no suitable constructor exists to convert from int () to <unnamed>::SomeClass for object_two and no suitable constructor exists to convert from int (*)() to <unnamed>::SomeClass for object_four.

I will also have to mention, that

std::function<int(void)> callable_1(some_function);
std::function<int(void)> callable_2 = some_function;
std::function<int(void)> callable_3(&some_function);
std::function<int(void)> callable_4 = &some_function;

works just fine. Am I missing something in the constructor of the class? What is happening here?


Solution

  • When you perform copy-initialization of some type T from a value of a given type U (which is what your "=" versions are doing), the system attempts to find a single conversion operation that takes a U and turns it into a T.

    But SomeClass has no such conversion. It can take a std::function to convert into a SomeClass, but you're not giving it a std::function. You're giving it a function pointer. And yes, a function pointer is convertible to a std::function. But you then have to convert that into SomeClass.

    C++ doesn't allow for two implicit user-defined conversions in a single expression like that. If it cannot do the conversion in a single user-defined step, you get an error.

    That doesn't happen in direct-initialization (the ones with parentheses) because you are explicitly saying to call a constructor of U (hence the parens). So the only conversion is from the given function type to a type appropriate to one of the parameters of an accessible constructor. The type is convertible in one step to std::function, so that constructor will be called.