The following code compiles fine:
#include <cstddef>
struct A {
char a;
static constexpr int off(void) { return offsetof(A, a); }
static constexpr int (*off_p)(void) = off;
};
The following seemingly similar code that just uses lambda for short, doesn't compile:
#include <cstddef>
struct A {
char a;
static constexpr int (*off_p)(void) =
[](void) static constexpr ->int { return offsetof(A, a); };
};
$ g++ -std=c++23 bad.cpp
In file included from /usr/include/c++/13/cstddef:50,
from bad.cpp:1:
bad.cpp: In static member function ‘A::<lambda()> static’:
bad.cpp:5:74: error: invalid use of incomplete type ‘struct A’
So basically I have 2 separate questions, since I don't understand what's going on here.
There's a finite list of places inside the member specification of a class (i.e. before the closing brace), where it is considered complete:
[class.mem.general]
7 A complete-class context of a class (template) is a
- function body ([dcl.fct.def.general]),
- default argument ([dcl.fct.default]),
- default template argument ([temp.param]),
- noexcept-specifier ([except.spec]), or
- default member initializer
within the member-specification of the class or class template.
8 A class C is complete at a program point P if the definition of C is reachable from P ([module.reach]) or if P is in a complete-class context of C. Otherwise, C is incomplete at P.
Now, while we may be tempted to think that a function body or default member initializer applies to the lambda case, they in fact do not. To start with, default member initializers appear in non-static members only (that's just how the standard terminology is laid out, static member initializers are defined as something else).
And as for "function body", note that the bullet refers to a relevant section where the grammar production of a function body is defined:
function-body: ctor-initializeropt compound-statement function-try-block = default ; = delete ;
On the other hand, the grammar for a lambda expression doesn't reuse the function-body
grammar at all:
lambda-expression: lambda-introducer attribute-specifier-seqopt lambda-declarator compound-statement lambda-introducer < template-parameter-list > requires-clauseopt attribute-specifier-seqopt lambda-declarator compound-statement
So with a lambda, we are in fact not in a complete class context, so paragraph 8 forces the compiler to treat A
as incomplete.