4

So I have been quite looking forward to metaclasses. I then heard that it won't be in , as they think we first need reflection and reification in the language before we should add metaclasses.

Looking over reflection, there appears to be reification capabilties. Are they sufficient to solve what metaclasses would do; ie, are metaclasses just syntactic sugar?

Using the current proposal, can we replicate someone writing a type like:

interface bob {
  void eat_apple();
};

and generating a type like:

struct bob {
  virtual void eat_apple() = 0;
  virtual ~bob() = default;
};

To go further, taking something similar to

vtable bob {
  void eat_apple();
  ~bob();
};
poly_value bob_value:bob {};

and being able to generate

// This part is optional, but here we are adding
// a ADL helper outside the class.
template<class T>
void eat_apple(T* t) {
  t->eat_apple();
}

struct bob_vtable {
  // for each method in the prototype, make
  // a function pointer that also takes a void ptr:
  void(*method_eat_apple)(void*) = 0;

  // no method_ to guarantee lack of name collision with
  // a prototype method called destroy:
  void(*destroy)(void*) = 0;

  template<class T>
  static constexpr bob_vtable create() {
    return {
      [](void* pbob) {
        eat_apple( static_cast<T*>(pbob) );
      },
      [](void* pbob) {
        delete static_cast<T*>(pbob);
      }
    };
  }
  template<class T>
  static bob_vtable const* get() {
    static constexpr auto vtable = create<T>();
    return &vtable;
  }
};
struct bob_value {
  // these should probably be private
  bob_vtable const* vtable = 0;
  void* pvoid = 0;

  // type erase create the object
  template<class T> requires (!std::is_base_of_v< bob_value, std::decay_t<T> >)
  bob_value( T&& t ):
    vtable( bob_vtable::get<std::decay_t<T>>() ),
    pvoid( static_cast<void*>(new std::decay_t<T>(std::forward<T>(t))) )
  {}
  
  ~bob_value() {
    if (vtable) vtable->destroy(pvoid);
  }

  // expose the prototype's signature, dispatch to manual vtable
  // (do this for each method in the prototype)
  void eat_apple() {
    vtable->method_eat_apple(pvoid);
  }

  // the prototype doesn't have copy/move, so delete it
  bob_value& operator=(bob_value const&)=delete;
  bob_value(bob_value const&)=delete;
};

Live example, both of which are examples of the kind of thing I was excited about metaclasses over.

I'm less worried about the syntax (being able to write a library and make creating the poly values or interfaces simply is useful, exact syntax is not) as much as I am concerned about it being capable of that.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • I'm not sure I understand the question. Metaclasses are just one aspect of reflection - they are syntax sugar for a particular kind of code injection. – Barry May 14 '21 at 19:49
  • @Barry I'm asking if the reflection proposal's existing reification capabilities is powerful enough to generate the same result as metaclasses can. Is is _just_ syntactic sugar? Or does the metaclass proposal provide more power? I **think** the answer is "just sugar", but I am not certain, hence the question. – Yakk - Adam Nevraumont May 14 '21 at 20:02
  • From [P2237](https://wg21.link/p2237): "The actual mechanism to make this work is a lexical trick; metaclasses are just syntactic sugar on top of the features described in Sections 6 and 7." – Barry May 14 '21 at 20:13
  • @Barry So that looks like a "we don't need metaclasses to do this" answer, and even a quote that shows it. :) – Yakk - Adam Nevraumont May 14 '21 at 23:13

1 Answers1

3

Looking over reflection, there appears to be reification capabilties. Are they sufficient to solve what metaclasses would do; ie, are metaclasses just syntactic sugar?

Calling it C++23 reflection is... optimistic. But the answer is yes. To quote from P2237:

metaclasses are just syntactic sugar on top of the features described [earlier]

As the paper points out, the metaclass syntax:

template<typename T, typename U>
struct(regular) pair{
    T first;
    U second;
};

means just:

namespace __hidden {
    template<typename T, typename U>
    struct pair {
        T first;
        U second;
    };
}

template <typename T, typename U>
struct pair {
    T first;
    U second;

    consteval {
        regular(reflexpr(pair), reflexpr(__hidden::pair<T, U>));
    }
};

where regular is some consteval function that injects a bunch of code. But in order for that to work at all, we need to have a language facility that supports a consteval function that injects a bunch of code. Metaclasses just provides a nice interface on top of that, but it's only a part of the kinds of things that hopefully we will be able to do with code injection.

Barry
  • 286,269
  • 29
  • 621
  • 977