Search code examples
c++c++17virtual-destructoraggregate-initialization

How can I aggregate init a struct that inherits from a virtual type?


As per [dcl.init.aggr] I cannot aggregate init a type, if it has (among other things) virtual functions, which includes inheriting from a type with a virtual destructor. However, I'd like to avoid having to write a ton of boilerplate constructors.

MWE:

struct Base {
  virtual ~Base() {}
};

struct Derived : Base {
  int i;
};

int main() {
  Derived d1{42}; // not allowed in this fashion
  Derived d2{{},42}; // also not allowed
}

In my setting I have a lot of types like Derived and they are all plain structs with a number of members (not necessarily trivial/pod) except for the fact that they have to inherit from Base.

Is there a way to avoid having to write Derived(int i) : Base(), i(i) {} constructors for all of them?


Solution

  • The one solution I could think of, is to exploit the fact that a struct without the inheritance above will gladly emit a default aggregate initialiser. So I compose that struct together with a templated wrapper type.

    template <typename T>
    struct BaseWrapper : Base, T {
      BaseWrapper(T data) : Node(), T(data) {}
      BaseWrapper() = delete;
      BaseWrapper(BaseWrapper const&) = default;
      BaseWrapper(BaseWrapper&&) = default;
      BaseWrapper& operator=(BaseWrapper const&) = default;
      BaseWrapper& operator=(BaseWrapper&&) = default;
      static T const& cast(Base const& b) {
        return static_cast<T const&>(static_cast<BaseWrapper<T> const&>(b));
      }
      static T& cast(Base& b) {
        return static_cast<T&>(static_cast<BaseWrapper<T>&>(b));
      }
    };
    

    And since I use the Derived types as shared pointers, a small convenience function:

    template <typename T, typename... Args>
    inline std::shared_ptr<BaseWrapper<T>> make_bw(Args&&... args) {
      return std::make_shared<BaseWrapper<T>>(T{std::forward<Args>(args)...});
    }
    

    Allows us to create objects without the need of a dedicated constructor inside the object:

    struct Derived { // note the missing : Base
      int i;
    };
    auto p = make_bw<Derived>(42);
    

    This is a bit of a cheaty solution, so a proper answer would still be useful.