Search code examples
c++c++11initializationstd-functionstdarray

Can not construct a class from std::function when used inside std::array


I want to have an std:array of std::function, but I want to make sure that all elements of the array are initialized. For that end I built a wrapper class that takes an std::function as a construction parameter.

But when I initialize an array of my wrapper class directly with my function (the one who should be inside std::function) it fails to compile.

Here is the problem, distilled:

#include <functional>
#include <array>

static void f() {}
using F = std::function<void(void)>;
enum { Count = 4 };

struct C
{
    //To get a compilation error when some
    //  elements of the array are not initialized.
    C() = delete;

    C(F) {}
};

//OK
static const C c {f};

//OK
static const std::array<F,Count> direct
{
    F{f},
    {f},
    f,
    f
};

static const std::array<C,Count> wrapper
{
    F{f},   //OK
    C{f},   //OK
    {f},    //OK
    f       //could not convert 'f' from 'void()' to 'C'
};

I tried changing the array to an std::vector<C> (although it defeats my whole purpose of using an std:array to begin with) and it refuses to compile any of the above initializations.


Solution

  • Unlike C c = f; (which is direct initialization), in aggregate initialization, every element is copy initialized.

    Each direct public base, (since C++17) array element, or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.

    That means, the last element of wrapper, which is of type C, is copy-initialized from f; which requiers two implicit conversions. The conversion from the function pointer to F, and the conversion from F to C. Both of them are user-defined conversions, but only one user-defined conversion is allowd in one implicit conversion sequence.

    For the same reason C c = f; fails either.

    You can add explicit conversion. e.g.

    static const std::array<C,Count> wrapper
    {
        F{f},   //OK
        C{f},   //OK
        {f},    //OK
        static_cast<F>(f)
    };
    

    static const C c {f}; works because it's direct initialization and behaves differently with copy initialization. For direct initialization the constructors of C will be considered and one of them expects an F as parameter, f could be converted to F then anything is fine; only one user-defined conversion is required here.

    (emphasis mine)

    In addition, the implicit conversion in copy-initialization must produce T directly from the initializer, while, e.g. direct-initialization expects an implicit conversion from the initializer to an argument of T's constructor.