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 ?
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 takingstd;:initializer_lists. Strongly. If there's any way for compilers to construe a call using a braced initializer to be a constructor taking astd::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
Tare 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
Tand 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 classTand 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With