Skip to content

[dcl.init] Aggregate initialization could take place when the initializer has the same type as the target, which is counter-intuitive #745

@cpplearner

Description

@cpplearner

Full name of submitter (unless configured in github; will be published with the issue): Tam S. B.

Reference (section label): [dcl.init.general] [dcl.init.list]

Link to reflector thread (if any):

Issue description:

[dcl.init.general]/(16.6):

  • Otherwise, if the destination type is a class type:

    • If the initializer expression is a prvalue [...]
    • Otherwise, if the initialization is direct-initialization, or if it is copy-initialization where the cv-unqualified version of the source type is the same as or is derived from the class of the destination type, constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one is chosen through overload resolution ([over.match]). Then:
      • If overload resolution is successful, the selected constructor is called to initialize the object, with the initializer expression or expression-list as its argument(s).
      • Otherwise, if no constructor is viable, the destination type is an aggregate class, and the initializer is a parenthesized expression-list, the object is initialized as follows. Let e1, …, en be the elements of the aggregate ([dcl.init.aggr]). Let x1, …, xk be the elements of the expression-list. If k is greater than n, the program is ill-formed. The element ei is copy-initialized with xi for 1 ≤ i ≤ k. The remaining elements are initialized with their default member initializers, if any, and otherwise are value-initialized. For each 1≤i<jn, every value computation and side effect associated with the initialization of ei is sequenced before those associated with the initialization of ej.
      • Otherwise, the initialization is ill-formed.

Consider

struct B;

struct A {
    A();
    A(A&);       // copy constructor
    A(const B&); // converting constructor
};

struct B {
    A a;
    // implicit default constructor: B();
    // implicit copy constructor: B(B&);
};

const B b1;
const B b2(b1);  // #1
const B b3{b1};  // #2

For b2, since neither B() nor B(B&) is viable, the parenthesized aggregate initialization takes place, which initializes b2 as if by const B b2{.a = b1}.

For b3, [dcl.init.list]/(3.2) is in effect, which reads:

If T is an aggregate class and the initializer list has a single element of type cv1 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).

This would invoke [dcl.init.general]/(16.6), but in this case, it's unclear whether the initializer is a a parenthesized expression-list, and thus unclear whether parenthesized aggregate initialization should apply.

Clang 21 (not yet released at the time of writing) permits both initializations, while all other compilers (including Clang 20.1.0) reject both.

In my opinion both initializations should be invalid.

Suggested resolution:

Modify [dcl.init.general]/(16.6.2.2) as indicated:

  • Otherwise, if no constructor is viable, the destination type is an aggregate class, and the initializer is a parenthesized expression-list, and, if the expression-list has a single element of type cv U, U is neither the same as nor derived from the class of the destination type, the object is initialized as follows. [...]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions