Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uniform initialization behavior different for different types in vector

Tags:

c++11

vector

I came across this article in which I read this example by one of the posters. I have quoted that here for convenience.

struct Foo
{
    Foo(int i) {} // #1
    Foo() {}
};

int main()
{
    std::vector<Foo> f {10};

    std::cout << f.size() << std::endl;
}

The above code, as written, emits “1” (10 is a converted to Foo by a constructor that takes an int, then the vector’s initializer_list constructor is called). If I comment out the line commented as #1, the result is “10” (the initializer_list cannot be converted so the int constructor is used).

My question is why does it emit a 10 if the int constructor is removed. I understand that uniform initialization list works in the following order

1-Calls the initializer list if available or possible
2-Calls the default constructor if available
3-Does aggregate initialization

In the above case why is it creating 10 items in the vector since 1,2 and 3 are not possible ? Does this mean with uniform initialization a vector of items might always have different behaviors ?

like image 298
James Franco Avatar asked Nov 21 '25 02:11

James Franco


1 Answers

Borrowing a quote from Scott Meyers in Effective Modern C++ (emphasis in original):

If, however, one or more constructors declare a parameter of type std::initializer_list, calls using the braced initialization syntax strongly prefer the overloads taking std;:initializer_lists. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking a std::initializer_list, compilers will employ that interpretation.

So when you have std::vector<Foo> f {10};, it will try to use the constructor of vector<Foo> that takes an initializer_list<Foo>. If Foo is constructible from an int, that is the constructor we're using - so we end up with one Foo constructed from 10.

Or, from the standardese, in [over.match.list]:

When objects of non-aggregate class type T are list-initialized (8.5.4), overload resolution selects the constructor in two phases:

(1.1) — Initially, the candidate functions are the initializer-list constructors (8.5.4) of the class T and the argument list consists of the initializer list as a single argument.
(1.2) — If no viable initializer-list constructor is found, overload resolution is performed again, where the candidate functions are all the constructors of the class T and the argument list consists of the elements of the initializer list.

If there is a viable initializer-list constructor, it is used. If you didn't have the Foo(int ) constructor, there would not be a viable initializer-list constructor, and overload resolution the second time around would find the constructor of vector that takes a size - and so you'd get a vector of 10 default-constructed Foos instead.

like image 50
Barry Avatar answered Nov 24 '25 22:11

Barry