Search code examples
c++templatesinheritancesuper

Mimic `super` keyword: Set `base` class and `derived` class field during instantiation


Consider the following code:

#include <iostream>

struct Parent {
  int baseA_ = 0;
  double baseB_ = 0.0;

  Parent() = default;
  virtual ~Parent() = default;

  Parent(int a) : baseA_{a} {}

  Parent(int a, double b) : baseA_{a}, baseB_{b} {}

  auto set(int a) -> void { baseA_ = a; }

  auto set(int a, double b) -> void {
    baseA_ = a;
    baseB_ = b;
  }
};

struct AnotherParent {
  std::string baseA_ = {};

  AnotherParent() = default;
  virtual ~AnotherParent() = default;

  AnotherParent(const std::string &a) : baseA_{a} {}

  auto set(const std::string &a) -> void { baseA_ = a; }
};

// Notice that the `Child` class inherits from T.
template <typename T>
struct Child : public T {
  int derivedA_ = 0;

  Child() = default;
  ~Child() = default;

  Child(int a) : derivedA_{a} {}

  auto super(auto... args) -> Child & {
    T::set(args...);
    return (*this);
  }
};

int main() {
  auto c1 = Child<Parent>(23).super(23, 3.5);
  auto c2 = Child<AnotherParent>(243).super("Hello, World!");

  std::cout << "C1 - Base -> A: " << c1.baseA_ << ", B: " << c1.baseB_
            << std::endl;
  std::cout << "C1 - Derived -> A: " << c1.derivedA_ << std::endl;

  std::cout << "C2 - Base -> A: " << c2.baseA_ << std::endl;
  std::cout << "C2 - Derived -> A: " << c2.derivedA_ << std::endl;
  return 0;
}

To compile use:

g++ -std=c++17 -fconcepts Main.cpp

or

g++ -std=c++14 -fconcepts Main.cpp

What I want to achieve?

I want to initialize both child class (inherited from template class type) and parent class fields during instantiation. I don't want to pass a parent class object as parameter to constructor.

Pros:

  1. It can be used to set super class (i mean it) data fields for any type.
  2. It offers safety by producing a compiler-error if set() method is not suitable.
  3. No need to overload child class constructor to set super class data fields (Infact, no one can overload child class constructor to match any super class, or is it possible?, I am not sure).
  4. Call to super() is optional.

Limitations:

  1. Works for single inheritance only, although interfaces can be implemented.
  2. Parent class should have a set() method.

Queries:

  1. The way how I implemented super - Is is a good practice?
  2. Are there any hidden cons?
  3. Does it violate any idiom?
  4. Is is ok to use -fconcepts?
  5. If it is good, can it be improved? Like, should I implement some interfaces to make it more maintainable or ensure availability of set() method.
  6. What are your opinions?

Solution

  • You could replace your super method with a template constructor that forwards to the base class.

    template <typename T>
    struct Child : public T {
      int derivedA_ = 0;
    
      Child() = default;
      ~Child() = default;
    
      Child(int a) : derivedA_{a} {}
    
      template <typename... Args>
      Child(int a, Args&&... args) : T{std::forward<Args>(args)...}, derivedA_{a} {}
    };
    

    Now you can use it like so

    auto c1 = Child<Parent>(23, 23, 3.5);
    auto c2 = Child<AnotherParent>(243, "Hello, World!");
    auto c3 = Child<Parent>(23, 42);
    

    Edit:

    Just a note on AnotherParents constructor. Since you pass in the std::string by const ref you will always have to copy it into the member.

    In that scenario, it's better to take the parameter by value. You will still at worst make one copy there too, but when we pass in an rvalue to the constructor we get a move instead of a copy.

    AnotherParent(std::string a) : baseA_{std::move(a)} {}