I believe what you're thinking of is from within a fragment https://cppx.godbolt.org/z/bazc9K:

class foo {
  consteval {
    -> fragment struct {
      foo bar() { // this isn't okay, foo is incomplete
        return {};
      }  
    };
  }
};

You can of course express the same idea, however you need to do this indirectly (so that injection can sew things together for you, at the right time):

class foo {
  consteval {
    -> fragment struct {
      typename |%{reflexpr(foo)}| bar() {
        return {};
      } 
    };
  }
};

Also, consider injection into another context (e.g. inject foo global_bar() { return { }; } into the global namespace):

    class foo {
      consteval {
        -> namespace(::) fragment namespace {
          typename |%{reflexpr(foo)}|
global_bar() {
            return {};
          }
        };
      }
    };

This injection is delayed until the class is complete, and then injected into the global namespace -- allowing the compiler to work with this code "in the usual way".


That means you can't rely on declarations assumed to have been injected

template<typename T>
struct my_class {
   consteval { -> inject_useful_decl<T>(); }
   decltype(useful_decl) var; // oops. error: no useful_decl declared.
};

Correct, this doesn't work, as the consteval block doesn't execute until the template is instantiated (https://cppx.godbolt.org/z/97v6Wr).


I don’t think we have to make consteval blocks part of the complete-class context of the enclosing class.  Lookup already works.  We just have to ensure that it is clear that some properties of nonstatic data members aren’t queryable.

struct S {
  int a;
  consteval {
    -> inject_useful_decl<typename(reflexpr(S::a))>(); // Okay.
  }
  consteval {
    -> inject_other<meta::offset_of(reflexpr(S::a))>();  // Error.
  }
};

A question we’d have to answer is whether a reflection of a nonstatic data member obtained while the class is still incomplete would remain valid after the class is completed, and, if so, whether the completeness would be reflected in that reflection (so that, e.g., querying the offset later on would work).  IMO, the answer should be yes to both of those.

Agreed, on all points.

On 11/2/20 2:03 PM, Andrew Sutton via SG7 wrote:

Default member initializers are parsed in a complete-class-context", essentially meaning the parsing is delayed until the closing } of the class.

Right, but Hana’s two examples aren’t in such contexts, and we permit them anyway.  Lookup succeeds, but some operations (like offsetof) aren’t permitted.

Wow. I am not reading well today.

Adding consteval blocks to the complete-class-context might make lookups work in metaprograms, but that also breaks this very simple model.

I don’t think we have to make consteval blocks part of the complete-class context of the enclosing class.  Lookup already works.  We just have to ensure that it is clear that some properties of nonstatic data members aren’t queryable.  E.g.:

struct S {
  int a;
  consteval {
    -> inject_useful_decl<typename(reflexpr(S::a))>(); // Okay.
  }
  consteval {
    -> inject_other<meta::offset_of(reflexpr(S::a))>();  // Error.
  }
};

A question we’d have to answer is whether a reflection of a nonstatic data member obtained while the class is still incomplete would remain valid after the class is completed, and, if so, whether the completeness would be reflected in that reflection (so that, e.g., querying the offset later on would work).  IMO, the answer should be yes to both of those.

I must be forgetting where lookup fails, but we've definitely run into issues requiring complete class types in *some* contexts. Wyatt can probably answer that question better than I can at this point.