Why is the std::initializer_list constructor prefe

2019-02-01 18:37发布

Consider the code

#include <iostream>

class Foo
{
    int val_;
public:
    Foo(std::initializer_list<Foo> il)
    {
        std::cout << "initializer_list ctor" << std::endl;
    }
    /* explicit */ Foo(int val): val_(val)
    {
        std::cout << "ctor" << std::endl;
    };
};

int main(int argc, char const *argv[])
{
    // why is the initializer_list ctor invoked?
    Foo foo {10}; 
}

The output is

ctor
initializer_list ctor

As far as I understand, the value 10 is implicitly converted to a Foo (first ctor output), then the initializer constructor kicks in (second initializer_list ctor output). My question is why is this happening? Isn't the standard constructor Foo(int) a better match? I.e., I would have expected the output of this snippet to be just ctor.

PS: If I mark the constructor Foo(int) as explicit, then Foo(int) is the only constructor invoked, as the integer 10 cannot now be implicitly converted to a Foo.

3条回答
时光不老,我们不散
2楼-- · 2019-02-01 19:04

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.

查看更多
贪生不怕死
3楼-- · 2019-02-01 19:16

§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 an explicit 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.

查看更多
唯我独甜
4楼-- · 2019-02-01 19:16

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);
查看更多
登录 后发表回答