C++: Curly Braces Initialization Despite Explicit Constructor

105 Views Asked by At

I've had a data class (so like a struct in the C sense: a class with only a bunch of properties) that I used to initialize (through an implicit copy constructor, I think) with curly braces initializer lists, like so:

ClassName inst = {1, 2, 3, 4};

I then decided I want to enable initialization from vectors, so I defined a ctor looking something like this:

ClassName(std::vector<int> vec) : A(vec[0]), B(vec[1]), C(vec[2]), D(vec[3]) {}

Ever since adding this constructor, though, the above-cited method of curly-braces-initialization no longer works due to "cannot conver from 'initializer list' to 'namespace::ClassName'".

I tried Googling for explanations/advice/solutions but couldn't find the right keywords and nothing I found seemed to answer my question exactly.

How would you advise getting around this (whether specifically how to re-enable such list initialization, or design-wise, if I should somehow avoid this scenario)?

2

There are 2 best solutions below

1
463035818_is_not_an_ai On BEST ANSWER

Consider this three classes:

#include <vector>
#include <initializer_list>

struct aggregate {
    int a,b,c,d;
};
struct non_aggregate {
    int a,b,c,d;
    non_aggregate(std::vector<int>) {}
};
struct init_list_constr {
    int a,b,c,d;
    init_list_constr(std::initializer_list<int>) {}
};

int main() {
    aggregate a = {1,2,3,4};
    //non_aggregate b = {1,2,3,4}; // error
    init_list_constr c = {1,2,3,4};
}

aggregate is an aggregate because it has no user defined constructor. You can use aggregate initialization: aggregate a = {1,2,3,4};. non_aggregate is not an aggregate because it has a user defined constructor and you cannot use aggregate initialization.

I suggest you to provide a std::initializer_list constructor which makes aggregate initialization possible also with a user defined constructor.

For details I refer you to https://en.cppreference.com/w/cpp/language/aggregate_initialization

3
pptaszni On

After adding the user-defined constructor your class is no longer an aggregate type (assuming it was before, because your question is incomplete), so the last rule of list initialization applies:

Otherwise, the constructors of T are considered, in two phases:

(...)

If the previous stage does not produce a match, all constructors of T participate in overload resolution against the set of arguments that consists of the elements of the braced-init-list, with the restriction that only non-narrowing conversions are allowed. (...).

So the compiler tries to pass 4 arguments of type int while your constructor expects 1 argument of type std::vector<int>. If you instead initialize your instance like this

ClassName inst = {{1, 2, 3, 4}};

compiler will pick your 1-argument constructor that expects std::vector<int> and then convert {1, 2, 3, 4} to std::vector<int> using its constructor taking the initializer_list.