Yamahari
Yamahari

Reputation: 2038

Constructors taking initializer lists

I get the idea behind uniform initialization with curly braces. But why is that using this syntax on types that have a constructor that takes an initializer list, calls that specific constructor, even though the arguments are only wrapped in a single pair of curly braces, i.e.

int main(int argc, const char ** argv)
{
    vector<int> vs0{3};

    for(int v : vs0)
    {
        cout << v << ' ';
    }

    cout << '\n';

    vector<int> vs1(3);

    for(int v : vs1)
    {
        cout << v << ' ';
    }
}

/*
    Output
    3
    0 0 0
*/

Why does vs0 get constructed with the initializer list constructor? Shouldn't it be

vector<int> v2{{3}};

for that? It's kind of confusing, especially if you're not aware that a class has a constructor that takes an initializer list.

Upvotes: 4

Views: 1124

Answers (2)

Arthur Tacca
Arthur Tacca

Reputation: 9978

It sounds like you're asking for motivation, as opposed to where in the standard it says this must be done. For that you can look at the original proposal N1919 for intializer lists by Bjarne Stroustrup, the C++ language creator.

He lists four ways to intialise an object:

X t1 = v; // “copy initialization” possibly copy construction
X t2(v); // direct initialization
X t3 = { v }; // initialize using initializer list
X t4 = X(v); // make an X from v and copy it to t4

Note that he's not talking about C++11, or a proposed version where initializer lists were introduced. This is back in C++98. That brace initialiser syntax already worked, but only for C-style structs i.e. with no user-defined constructor. This is a holdover from C, which has always allowed initialising structs (and arrays) this way, and it will always do the same thing: initialise element-by-element.

The whole point of the proposal is to allow initialisation for proper C++ objects like std::vector<int> in the same way as those C-style structs and arrays: C++ is meant to allow user-defined classes to look like built-in types (hence e.g. operator overloading) and here's one place it didn't. To turn your question around, the thing that is odd is not that std::vector<int>{3} calls the initialiser list constructor, the thing that is odd is that std::vector<std::string>{3} calls the non-initialiser list constructor. Why does it ever call a non-initialiser list constructor? That's not really what brace initialisation was originally for. The answer to that is to allow fix-length containers with hand-written constructors, like this:

class Vector3D {
public:
    Vector3D(double x, double y, double z) { /*...*/ }
    // ...
};
Vector3D v = {1, 2, 3}; // Ought to call non-initialiser list constructor

That is why constructors taking std::initializer_list are preferred (if available) when using brace initialisation. For those knowing the background, the use of brace initialisers for everything, as has become the fashion, seems really perverse: Foo f{7} looks like f will directly contain the number 7 and nothing else after construction completes, not that it does something arbitrary like construct something 7 elements long.

Upvotes: 1

songyuanyao
songyuanyao

Reputation: 172864

If the class has a constructor taking std::initializer_list, then it'll be preferred when being passed a braced-init-list in list initliazation.

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

    • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list

    • 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. If this stage produces an explicit constructor as the best match for a copy-list-initialization, compilation fails (note, in simple copy-initialization, explicit constructors are not considered at all).

{3} is a braced-init-list, then the vector will be initialized as containing 1 element with value 3. The behavior is consistent with passing {1, 2}, {1, 2, 3} and so on.

Upvotes: 2

Related Questions