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.
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.