List-initialization priority from object of same t

2019-01-29 02:52发布

问题:

#include <iostream>
#include <initializer_list>
using namespace std;

struct CL
{
    CL(){}
    CL (std::initializer_list<CL>){cout<<1;}
    CL (const CL&){cout<<2;}
};

int main()
{
    CL cl1;
    CL cl2 {cl1}; //prints 21
}

Here is CL struct with copy constructor and initializer-list constructor. I think only copy constructor must be called here, because according to C++ 14 Standard, 8.5.4/3

List-initialization of an object or reference of type T is defined as follows:
— If T is a class type and the initializer list has a single element of type cv U, where U is T or a class derived from T, the object is initialized from that element (by copy-initialization for copy-list-initialization, or by direct-initialization for direct-list-initialization).
— Otherwise, ...

In other words, the initialization of cl2 must be performed from cl1 element, but not from the initializer-list {cl1}. Clang and gcc both print "21", only Visual Studio prints "2" and I think it's correct.
There are two candidate constructors for taking an argument cl1 of type CL:
1) Constructor with std::initializer_list<CL> (passes because no such conversion from CL to std::initializer_list<CL>)
2) Copy constructor with const CL& (exact match with only qualification conversion non-const->const)

Who is right? Whose behavior is correct?

Thanks

回答1:

tl;dr: The published C++14 text specified output 21. However, the behaviour of this code was changed by CWG Issue 1467, which gained the status of Defect in November 2014.

Defect Reports are considered to apply retroactively. clang 3.7 and VS2015 have applied the resolution suggested by this defect report, which appears in C++17 drafts as of N4296.


Prior to this defect report, the behaviour was covered by this text from N4140 [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:

  • 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. [Note: This differs from other situations (13.3.1.3, 13.3.1.4), where only converting constructors are considered for copy-initialization. This restriction only applies if this initialization is part of the final result of overload resolution. —end note ]

Your class is not an aggregate because it has a user-provided constructor.

The above text is directed-to by the following bullet point in [dcl.init.list]/3:

  • Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).

So, in the published C++14, the initializer-list constructor should actually be preferred to the copy-constructor, if it matches. C++11 had the same text.


In your question you say that C++14 contains:

If T is a class type and the initializer list has a single element of type [...]

This text was not in C++14, but applied at a later date by a defect report. In the updated standard with the defect report applied (N4296), this appears as a bullet point higher up in the list of bullet points in [dcl.init.list]/3; so now the copy-constructor is selected ealier in the process and we do not get so far as the above [over.match.list] step.

Note that although the defect is titled List-initialization of aggregate from same-type object, the resolution actually affects initialization of both aggregates and non-aggregates.