cplusplus / CWG

Core Working Group
23 stars 7 forks source link

CWG2852 [class.mem.general] Is a default argument of a class-scope lambda a complete-class context? #449

Open zygoloid opened 11 months ago

zygoloid commented 11 months ago

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

Reference (section label): [class.mem.general]/7

Link to reflector thread (if any): None, but GCC bugzilla thread: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111923

Issue description:

[class.mem.general]/7 is overly broad:

A complete-class context of a class (template) is a (7.1) function body ([dcl.fct.def.general]), (7.2) default argument ([dcl.fct.default]), (7.3) default template argument ([temp.param]), (7.4) noexcept-specifier ([except.spec]), or (7.5) default member initializer within the member-specification of the class or class template.

The "within" here appears to cover such things appearing indirectly within the member-specification, such as a default argument or default template argument of a class-scope lambda-expression:

struct X {
  static constexpr auto a = [] (int n = X::n) {};
  static constexpr int n = 5;
};

Similarly, the current rule appears to treat noexcept-specifiers and default arguments appearing as part of all function types in the class body as complete-class context, rather than only those introduced in the declaration of a member or friend function:

struct Y {
  // *noexcept-specifier* is a complete-class context,
  // but presumably was not intended to be.
  void (*p)(int n) noexcept(sizeof(Y) == 1);
};

Suggested resolution:

Change in [class.mem.general]/7:

A complete-class context of a class (template) or class template C is a — function body ([dcl.fct.def.general]), — default argument ([dcl.fct.default]) of a function declaration, — default template argument ([temp.param]), — noexcept-specifier ([except.spec]) of a function declaration, or — default member initializer, or — complete-class context of a nested class defined in C, recursively where the function, template, non-static data member, or nested class is declared by a member-declaration of C. within the member-specification of the class or class template. [Note 4: A complete-class context of a nested class is also a complete-class context of any enclosing class, if the nested class is defined within the member-specification of the enclosing class. — end note]

stsp commented 11 months ago

The aforementioned bugzilla thread was intended to point out that some standard-compliant code is currently rejected by gcc. Namely, this code was supposed to work:

template <auto O>
struct B {
    static constexpr int off = O();
};

struct A {
    char a;
    B<[]() static constexpr ->int { return offsetof(A, b); }> b;
};

due to [class.mem.general]/7.1 (function body) + note4 for closure sub-class.

The suggested resolution should be to explicitly list "closure type" in "Note 4", rather than to rephrase and exclude the "closure type" from a "complete class context" candidates.

jensmaurer commented 8 months ago

@stsp: No, not as a core issue. Please write a paper targeted at EWG if you wish to extend the complete-class contexts; there are non-trivial implementation concerns.

CWG2852

stsp commented 8 months ago

@jensmaurer Well, all I need is to use offsetof() in templates. If you tell me how to do that, I won't need to write any EWG paper. But so far, every once I find a way to do that, it immediately gets disallowed. First one was reinterpret_cast in constexpr, which could trivially be used to calc an offset - disallowed, even if it actually worked and my code was using it. Lambdas in class-complete context - disallowed. Come one, just tell me how to use offsetof() then... Don't only disallow, give me a work-around!

jensmaurer commented 8 months ago

EWG is the right group to discuss extensions to the language, not this forum. Side note: CWG2784 discusses an issue with interpreting the term "member-designator" from C's specification of "offsetof" in a C++ context.

zygoloid commented 8 months ago

@stsp Instead of adding another complete-class context, you can make the class type dependent and defer performing instantiation until the class is complete: https://godbolt.org/z/sjbKGnqva

(This is off-topic for this forum, but hopefully this example can save you and EWG some time.)

stsp commented 8 months ago

That works, thank you! Just a few minor downsides:

But its a working solution.