2
votes

I'm trying to initialize a class Vec with a brace-enclosed initializer list, to be called like in this minimal example:

int main()
{
    Vec<3> myVec1{{1,2,3}}; // or: myVec1({1,2,3});
    Vec<3> myVec2;
    myVec2 = {1, 2, 3};
    Vec<3> myVec3 = {1,2,3};
    return 0;
}

All these initializations (and the assignment) should work. (Plus custom default constructor. Using an aggregate class is thus impossible.)

While I could just use a std::initializer_list like such:

template <unsigned C>
struct Vec
{
    Vec(){}
    Vec(std::initializer_list<int> list)
    {
        //does not work for obvious reasons:
        //static_assert(list.size() == C, "");
    }
};

I can't statically ensure the number of parameters to be equal to my template parameter, i.e., an initialization with {1, 2, 3, 4} would only fail during runtime.

Browsing SO, I came up with the following:

template <unsigned C>
struct Vec
{
    Vec(){}
    Vec(const unsigned(&other)[C]){}
    Vec& operator=(const unsigned(&other)[C]){return *this;}
};

This works fine for the assignment and () or {} initialization (as for myVec1) - but it fails for the initialization using = (as for myVec3).

(GCC gives error "could not convert '{1, 2, 3}' from '' to 'Vec<3u>'")

I don't get why one of the initializations should work, but not the other. Any other ideas how I can use the brace-enclosed initializer list, but also ensure the correct length at compile time?

Thanks :)

2
Maybe variadic templates with a static_assert or just sfinae with enable_if. - DeiDei

2 Answers

1
votes

Initializer lists are more appropriate for situations where the size of the list is dynamic. In your case, where the size of Vec is a template parameter (i.e. static), you're probably better off with variadic parameters:

template <unsigned C>
struct Vec
{
    Vec(){}

    template<typename ... V>
    Vec(V ... args)
    {
        static_assert(sizeof...(V) == C, "");
        //...
    }
};

then these will work:

Vec<3> myVec2;
myVec2 = {1, 2, 3};
Vec<3> myVec3 = {1,2,3};

But this one won't, as it explicitly requires an std::initializer_list:

Vec<3> myVec1{{1,2,3}};
0
votes

You need two pair of brackets:

Vec<3> myVec3 = {{1,2,3}};

When you do:

Vec<3> myVec3 = {1, 2, 3};
// It is the same as (except that explicit constructor are not considered):
Vec<3> myVec3 = Vec<3>{1, 2, 3};'

So basically, the compiler will look for a constructor for Vec<3> which either takes an std::initializer_list or a constructor that takes 3 arguments, all of which being constructible from int.

If you want a std::array like structure, you need to make your type an aggregate, meaning that it should not have user-defined constructor:

template <unsigned N>
struct Vec {
  int data[N];
};

void f() {
  Vec<3> v1{{1, 2, 3}};
  Vec<3> v2 = {{1, 2, 3}};
}