cplusplus / CWG

Core Working Group
23 stars 7 forks source link

[intro.execution] p4 The potentially-evaluated subexpressions of an empty initializer list #473

Closed xmh0511 closed 10 months ago

xmh0511 commented 10 months ago

Full name of submitter (unless configured in github; will be published with the issue): Jim X

Consider this case:

#include <any>
template<class T>
struct B{T b;};

struct C{
    std::any v = B<C>{};  // #1
};

[dcl.init.aggr] p13 says:

If a member has a default member initializer and a potentially-evaluated subexpression thereof is an aggregate initialization that would use that default member initializer, the program is ill-formed.

[intro.execution] p4 says:

The potentially-evaluated subexpressions of an expression, conversion, or initializer E are

  • the constituent expressions of E and
  • the subexpressions thereof that are not subexpressions of a nested unevaluated operand ([expr.context]).

[intro.execution] p2 says:

A constituent expression is defined as follows:

  • [...]
  • The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the constituent expressions of the elements of the respective list.
  • The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the constituent expressions of the initializer-clause.

B<C>{} at #1 cause the aggregate initialization of B<C> as per [expr.type.conv] p2, which is a default member initializer

Otherwise, the expression is a prvalue of the specified type whose result object is direct-initialized with the initializer.

That is, the initializer is as if it was {}, however, this is an empty initialization list, which has an empty elements, that is, there is no subexpression of {}. However, the major implementations say the example is ill-formed, which most likely is regulated by [dcl.init.aggr] p13.

Moreover, [dcl.init.aggr] p5 says:

For a non-union aggregate, each element that is not an explicitly initialized element is initialized as follows:

  • If the element has a default member initializer ([class.mem]), the element is initialized from that initializer.
  • Otherwise, if the element is not a reference, the element is copy-initialized from an empty initializer list ([dcl.init.list]).

That is, B<C>::b is copy-initialized from {}. The whole default member initializer, as per current wording, is at best {{}}.

Suggested Resolution

In this example, we may want to expect C{} as the thereof subexpression of the default member initializer B<C>{}, and C{} cause the aggregate initialization that would use B<C>{}.

jensmaurer commented 10 months ago

No.

If a member has a default member initializer and a potentially-evaluated subexpression thereof is an aggregate initialization that would use that default member initializer, the program is ill-formed.

The default member initializer is = B<C>{}, and the subexpression B<C>{} is an aggregate initialization, and that subexpression would use the default member initializer because of the default initialization of C b. The "would use" part doesn't talk about (syntactic) subexpressions anymore.

xmh0511 commented 10 months ago

The "would use" part doesn't talk about (syntactic) subexpressions anymore.

Consider this counter-example:

#include <any>
template<class T>
struct B{T b;};

struct C{
    C(){}  // introduce a user-declared constructor
    std::any v = B<C>{};
};

int main(){
    //C c{};
}

In this example, the class C is not an aggregate due to the user-declared constructor, however, B<C> is still an aggregate, all implementations just accept this example.

jensmaurer commented 10 months ago

Sure, but that's because the default initialization of C b doesn't (directly) use the default member initializer; it uses the user-provided default constructor of C. I'm not seeing a problem with the definition of "subexpression" here, as originally claimed.

xmh0511 commented 10 months ago

it uses the user-provided default constructor of C.

[class.base.init] p9 says:

In a non-delegating constructor other than an implicitly-defined copy/move constructor ([class.copy.ctor]), if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then

-[...]

  • if the entity is a non-static data member that has a default member initializer ([class.mem]) and either
    • [...]
      • the constructor's class is not a union, and, if the entity is a member of an anonymous union, no other member of that union is designated by a mem-initializer-id,

    the entity is initialized from its default member initializer as specified in [dcl.init];

The used default constructor of C uses the default member initializer, which is subsumed to be used by the aggregate initialization, as you said above: doesn't talk about (syntactic) subexpressions anymore.