lambda function with incomplete type

250 views Asked by At

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.

  1. Why in the first case the use of incomplete type is allowed?
  2. Why in the second case the use of incomplete type is NOT allowed?

Compiler explorer demo.

1

There are 1 answers

7
StoryTeller - Unslander Monica On BEST ANSWER

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.