no match for operator '=' (std::array<T, 3> and std::initializer_list<T>)

Viewed 392

I have a template vec<T> class for math vectors with constant length is equal to 3. It looks like this:

template <typename T>
class vec{
public:
    vec() { /**/ }

    vec(std::initializer_list<T> list) { /**/ }

private:
    std::array<T, 3> data;
};

I know, that it is possible to initialize std::array with an std::initializer_list in such a way:

std::array<int, 3> a = {1, 2, 3};

So I would like to have a constructor with an std::initializer_list to initialize my vector like this:

vec<int> v = {1, 2, 3};

I also have a solution for this: just iterate through std::initializer_list elements and write to data:

vec(std::initializer_list<T> list) {
    size_t i = 0;
    for (auto it = list.begin(); it != list.end(); it++) {
        data[i++] = *it;
    }
}

I tried to make constructor look like this (because of std::array has a constructor with std::initializer_list):

vec(std::initializer_list<T> list) {
    data = list;
}

But when I try to compile a program, the error occurs:

no match for ‘operator=’ (operand types are ‘std::array<int, 3>’ and ‘std::initializer_list<int>)’

What's the problem here? Why can't I initialize std::array with initializer list in such a way?

3 Answers

Why can't I initialize std::array with initializer list in such a way?

Braced-init-list and std::initializer_list are not the same thing. (Even std::initializer_list could be constructed from braced-init-list.) std::array is an aggregate and could be aggregate-initialized or assigned by braced-init-list as

std::array<int, 3> a = {1, 2, 3};
a = {4, 5, 6}; // convert {4, 5, 6} to std::array then assign to a

Note that both {1, 2, 3} and {4, 5, 6} are braced-init-list but not std::initializer_list.

std::array can't be initialized (or assigned) from an std::initializer_list; its constructors and assignment operator are implicitly-defined and doesn't have such constructor or assignment operator taking std::initializer_list.

std::initializer_list<int> l = {1, 2, 3};
std::array<int, 3> a = l; // doesn't work
a = l;                    // doesn't work

As a possible work-around, you could add a constructor overload taking a std::array instead, and use a constructor initializer list (not to be confused with std::initializer_list) to initialize data:

vec(std::array<T, 3> const& list)
    : data{ list }
{
}

You can think of std::initializer_list as a container class that works specifically for ctor parameters with the obj{args...} syntax.

Underneath, it contains a temporary c-array, with size() and pointer to begin() and end(). And there isn't a implicit conversion between std::initializer_list to std::array.

Other posts already mentioned ways of doing it, another way you could do is

std::copy(list.begin(), list.begin() + data.size(), data.begin());

This would work even if your list.size() is larger than data.size().

Do note that if list.size() is smaller than data.size(), anything pass beyond list.size() would be default initialized, which would cause ub for scalar types.

Related