This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 116a. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.

2024-12-19


2856. Copy-list-initialization with explicit default constructors

Section: 12.2.2.8  [over.match.list]     Status: DRWP     Submitter: Anoop Rana     Date: 2024-01-09

[Accepted as a DR at the March, 2024 meeting.]

(From submission #486.)

Consider:

  struct A {
     explicit A(int = 10);
     A() = default;   // converting constructor (11.4.8.2 [class.conv.ctor] paragraph 1)
  };

  A a = {};       // #1, copy-initialization
  
  int f(A);
  int x = f({});  // #2

  A b;            // #3

#1 and #2 are accepted by MSVC and EDG, but considered ambiguous by gcc and clang. #3 is rejected as ambiguous by all major implementations.

#1 is copy-list-initialization (9.4.5 [dcl.init.list] paragraph 1), and A has a default constructor, thus a is value-initialized (9.4.5 [dcl.init.list] bullet 3.4). The default constructors are user-provided, thus a is default-initialized (9.4.1 [dcl.init.general] bullet 8.1, 9.4.1 [dcl.init.general] bullet 7.1). Overload resolution then chooses a constructor according to 12.2.2.4 [over.match.ctor] paragraph 1:

... For copy-initialization (including default initialization in the context of copy-initialization), the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. ...

Thus, the explicit constructor is not a candidate; #1 chooses the converting constructor.

In contrast, #2 uses the special rules for forming a list-initialization sequence (12.2.4.2.6 [over.ics.list] paragraph 7), which perform overload resolution according to 12.2.2.8 [over.match.list] paragraph 1, which considers all constructors for overload resolution (and makes the program ill-formed if an explicit constructor is chosen). For the example, overload resolution is ambiguous.

#3 performs default-initialization, and overload resolution then chooses a constructor according to 12.2.2.4 [over.match.ctor] paragraph 1:

... For direct-initialization or default-initialization that is not in the context of copy-initialization, the candidate functions are all the constructors of the class of the object being initialized. ...

In this case, the overload resolution is ambiguous.

Proposed resolution (approved by CWG 2024-02-16):

Change in 12.2.2.4 [over.match.ctor] paragraph 1 as follows:

When objects of class type are direct-initialized (9.4 [dcl.init]), copy-initialized from an expression of the same or a derived class type (9.4 [dcl.init]), or default-initialized (9.4 [dcl.init]), overload resolution selects the constructor. For direct-initialization or default-initialization that is not in the context of copy-initialization (including default-initialization in the context of copy-list-initialization), the candidate functions are all the constructors of the class of the object being initialized. For copy-initialization (including default initialization in the context of copy-initialization) Otherwise, the candidate functions are all the converting constructors (11.4.8.2 [class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer. For default-initialization in the context of copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.

CWG 2024-02-16

The fact that #2 considers all constructors was discussed (and established) in the C++11 timeframe when brace-initialization was first introduced. #1 should be consistent with that, even though the = is usually a clear sign that explicit constructors are not considered.