Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
zygoloid opened this issue Oct 24, 2023 · 6 comments

Comments

@zygoloid
Copy link
Member

zygoloid commented Oct 24, 2023

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
Copy link

stsp commented Oct 25, 2023

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
Copy link
Member

jensmaurer commented Feb 3, 2024

@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

@jensmaurer jensmaurer changed the title [class.mem.general] Is a default argument of a class-scope lambda a complete-class context? CWG2852 [class.mem.general] Is a default argument of a class-scope lambda a complete-class context? Feb 3, 2024
@stsp
Copy link

stsp commented Feb 4, 2024

@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
Copy link
Member

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
Copy link
Member Author

zygoloid commented Feb 4, 2024

@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
Copy link

stsp commented Feb 5, 2024

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

  • c++-20-specific
  • such lambda seems to be no longer convertible to function pointer, as normal lambdas are

But its a working solution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants