Consider the code
#include
class Foo
{
int val_;
public:
Foo(std::initializer_list il)
{
std::cout <<
§13.3.1.7 [over.match.list]/p1:
When objects of non-aggregate class type
T
are list-initialized (8.5.4), overload resolution selects the constructor in two phases:
- 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.- 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 the initializer list has no elements and
T
has a default constructor, the first phase is omitted. In copy-list-initialization, if anexplicit
constructor is chosen, the initialization is ill-formed.
As long as there is a viable initializer-list constructor, it will trump all non-initializer-list constructors when list-initialization is used and the initializer list has at least one element.
The n2100 proposal for initializer lists goes into great detail about the decision to make sequence constructors (what they call constructors that take std::initializer_lists
) to have priority over regular constructors. See Appendix B for a detailed discussion. It's succinctly summarized in the conclusion:
11.4 Conclusion
So, how do we decide between the remaining two alternatives (“ambiguity” and “sequence constructors take priority over ordinary constructors)? Our proposal gives sequence constructors priority because
- Looking for ambiguities among all the constructors leads to too many “false positives”; that is, clashes between apparently unrelated constructors. See examples below.
- Disambiguation is itself error-prone (as well as verbose). See examples in §11.3.
- Using exactly the same syntax for every number of elements of a homogeneous list is important – disambiguation should be done for ordinary constructors (that do not have a regular pattern of arguments). See examples in §11.3. The simplest example of a false positive is the default constructor:
The simplest example of a false positive is the default constructor:
vector<int> v; vector<int> v { }; // potentially ambiguous void f(vector<int>&); // ... f({ }); // potentially ambiguous
It is possible to think of classes where initialization with no members is semantically distinct from default initialization, but we wouldn’t complicate the language to provide better support for those cases than for the more common case where they are semantically the same.
Giving priority to sequence constructors breaks argument checking into more comprehensible chunks and gives better locality.
void f(const vector<double>&); // ... struct X { X(int); /* ... */ }; void f(X); // ... f(1); // call f(X); vector’s constructor is explicit f({1}); // potentially ambiguous: X or vector? f({1,2}); // potentially ambiguous: 1 or 2 elements of vector
Here, giving priority to sequence constructors eliminates the interference from X. Picking X for f(1) is a variant of the problem with explicit shown in §3.3.
The whole initializer list thing was meant to enable list initialisation like so:
std::vector<int> v { 0, 1, 2 };
Consider the case
std::vector<int> v { 123 };
That this initializes the vector with one element of value 123 rather than 123 elements of value zero is intended.
To access the other constructor, use the old syntax
Foo foo(10);