Do we need metaclasses to do this, or is reflection enough?

426 views Asked by At

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.

1

There are 1 answers

0
Barry On BEST ANSWER

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.