On Nov 2, 2020, at 11:00 AM, Andrew Sutton via SG7 <sg7@lists.isocpp.org> wrote:


This seems counterintuitive to me. I consider `sizeof` and `decltype` a poor version of a reflection and you can do this:

struct X {
int a;
static constexpr unsigned size = sizeof(X::a);
using type = decltype(X::a);
};


Even when the type is not yet complete. I personally think we should support such cases.

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.

This is why lookup works in those cases. The same is true of default arguments, inline member function bodies, and noexcept-specifiers.

My mental model of injection via conseval blocks is very simple. First, consteval blocks have the guarantee that they are executed where they appear or where they're instantiated if they appear in a template. After the metaprogram runs, the compiler effectively "pastes" the injected code over top of the metaprogram as if you had manually typed in. Of course, whatever rules and restrictions apply to hand-written code also apply to metaprograms.

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.

Daveed


Consteval blocks are no longer guaranteed to execute where they appear, but rather sometime later. 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.
};

I think it also makes reasoning about injection much harder because the complete-class-context defers analysis to the outermost enclosing class.

struct s {
  struct  t {
    consteval { /* inject code */ };
  }; // metaprogram is not run here
}; // metaprogram runs here

This potentially makes the implementation a bit harder too, since we'd have to re-establish the full context where the consteval block was parsed before executing it.

Andrew
-- 
SG7 mailing list
SG7@lists.isocpp.org
https://lists.isocpp.org/mailman/listinfo.cgi/sg7