Search code examples
c++c++14initializer-liststdarray

Forwarding intializer list / aggregate inialization to std::array member


I have a class that encapsulates a stl compatible container type which is the only class member and gives a lot of vector math functions that can be applied on this vector.

This class has various kinds of constructors, one of them is a constructor that takes an initialiser list:

template <class Type, class VectorType = std::vector<Type>>
class MathVector
{
public:
    using initializer_list = std::initializer_list<Type>;

    MathVector (initializer_list il) : vector(il) {}

    // many more constructors and member functions here

private:
    VectorType vector:
}

While something like MathVector<int> foo = { 1, 2, 3 } works well, MathVector<int, std::array<int, 3>> bar = { 1, 2, 3 } fails to compile on clang with an error like

(86, 55) No matching constructor for initialization of 'std::__1::array<int, 3>'

std::vector<int> foo = { 1, 2, 3 } and std::array<int, 3> bar = { 1, 2, 3 } works, so I guess that despite the same syntax, std::array is not really constructed through an initializer list in this case. This guess gets stronger when looking into the std library sources where I don't find any initializer list based std::array constructor. Furthermore, cppreference tells me that it can be initialized with aggregate-initialization – which does not even seem to be any kind of usual constructor. So is there a way to create a constructor for my class that correctly forwards an initialization with the desired syntax to a std::array member?


Solution

  • With tag-dispatching:

    template <typename T>
    struct type_identity { using type = T; };
    
    template <typename Type, typename VectorType = std::vector<Type>>
    class MathVector
    {
    public:
        MathVector(std::initializer_list<Type> il)
            : MathVector(il, type_identity<VectorType>{}) {}
    
    private:
        template <typename T>
        MathVector(std::initializer_list<Type> il, type_identity<T>)
            : vector(il) {}
    
        template <typename T, std::size_t N>
        MathVector(std::initializer_list<Type> il, type_identity<std::array<T, N>>)
            : MathVector(il, std::make_index_sequence<N>{}) {}
    
        template <std::size_t... Is>
        MathVector(std::initializer_list<Type> il, std::index_sequence<Is...>)
            : vector{ *(il.begin() + Is)... } {}
    
        VectorType vector;
    };
    

    DEMO


    An alternative solution is to use a variadic template constructor.

    template <typename Type, typename VectorType = std::vector<Type>>
    class MathVector
    {
    public:
        template <typename... Ts>
        MathVector(Ts&&... ts)
            : vector{ std::forward<Ts>(ts)... } {}
    
        MathVector(MathVector& rhs)
            : MathVector(const_cast<const MathVector&>(rhs)) {}
    
        MathVector(const MathVector& rhs)
            : vector(rhs.vector) {}
    
    private:
        VectorType vector;
    };
    

    DEMO 2

    or shorter in :

    template <typename Type, typename VectorType = std::vector<Type>>
    class MathVector
    {
    public:
        MathVector(std::convertible_to<Type> auto&&... ts)
            : vector{ std::forward<decltype(ts)>(ts)... } {}
    
    private:
        VectorType vector;
    };
    

    DEMO 3