Search code examples
c++templatesinheritance

Initialize an object based on any sub-class of an abstract base class


I've got a templated abstract base class A and a number of classes that inherit from it:

template <typename T>
class A {
virtual void do_something() = 0;
};

template <typename T>
class B : public A<T> {
virtual void do_something() override {...}
};

template <typename T>
class C : public A<T> {
virtual void do_something() override {...}
}; 

template <typename T>
class D : public A<T> {
virtual void do_something() override {...}
}; 

Now I want to define a class that manages an array of any one of these objects. So for example:

auto cm = Multi<double, 3>(c); // Holds an array of 3 C<double>
auto bm = Multi<long, 5>(b); // Holds an array of 5 B<long>

What is the correct way to define this class so that it can be initialized with the type of object it is holding (B,C,D, etc.), ideally without creating named objects that don't get used?

I know I can use pointers to the base class A to make the array class generic, but I can't figure out how to get the class to initialize effectively.

I would be ok using templating on the Multi class to have it templated to a specific type (B,C, or D), but I haven't found a way of restricting the template to accept only subclasses of A.

Edit:

Here's what I want for the multi. Essentially I want to be able to call Multi.do_something() and have it call that method for each object in its array. The example also shows the methods that haven't worked for me.

template <typename T, int N>
class Multi {
public:
  Multi(A<T> obj){  // Doesn't work because you can't create an instance of A
    for (auto obj : _objs){
        obj = std::make_unique<obj>();
    }
  }

  Multi(A<T> *obj) { // Could work, but doesn't work for anonymous objects because they're r-values
     ...
  }
  
  void do_something(){
     for (auto obj : _objs){
        obj->do_something();
     }
  }
private:
  std::array<std::unique_ptr<A<T>>, N> _objs;
};

Solution

  • Here's an example using a std::variant approach, and your Multi approach with std::unique_ptr.

    #include <array>
    #include <cstddef>
    #include <memory>
    #include <iostream>
    #include <string>
    #include <variant>
    
    using std::array;
    using std::cout;
    using std::make_unique;
    using std::size_t;
    using std::string;
    using std::unique_ptr;
    using std::variant;
    using std::visit;
    
    using unit = std::monostate;
    
    template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
    // Explicit deduction guide needed for C++17, not needed as of C++20.
    template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
    
    template <typename T>
    struct A {
        virtual ~A() = default;
        A() = default;
        A(A const&) = default;
        auto operator=(A const&) -> A& = default;
        virtual void some_function() = 0;
        virtual auto clone() const -> unique_ptr<A> = 0;
    };
    
    template <typename T>
    struct B : A<T> {
        void some_function() override {
            cout << "B some_function\n";
        }
        virtual auto clone() const -> unique_ptr<A<T>> override {
            return make_unique<B>(*this);
        }
    };
    
    template <typename T>
    struct C : A<T> {
        void some_function() override {
            cout << "C some_function\n";
        }
        virtual auto clone() const -> unique_ptr<A<T>> override {
            return make_unique<C>(*this);
        }
    };
    
    template <typename T>
    struct D : A<T> {
        void some_function() override {
            cout << "D some_function\n";
        }
        virtual auto clone() const -> unique_ptr<A<T>> override {
            return make_unique<D>(*this);
        }
    };
    
    template <typename T, int N>
    class Multi {
    public:
        Multi(A<T> const& obj){  // Doesn't work because you can't create an instance of A
            for (auto& ptr : _objs){
                ptr = obj.clone();
            }
        }
    
        void do_something() {
            for (auto& ptr : _objs){
                ptr->some_function();
            }
        }
    
        void set(size_t i, A<T> const& obj) {
            _objs[i] = obj.clone();
        }
    
    private:
        array<unique_ptr<A<T>>, N> _objs;
    };
    
    int main() {
        using BB = B<int>;
        using CC = C<double>;
        using DD = D<string>;
        using var_t = variant<unit, BB, CC, DD>;
        array<var_t, 10> a;
        a[0] = BB();
        a[3] = CC();
        a[8] = DD();
    
        size_t i{};
        for (auto&& x : a) {
            cout << "a[" << i++ << "]: ";
    
            visit(overloaded{
                [](unit) { cout << "{}\n"; },
                [](auto& aa) { aa.some_function(); },
            }, x);
        }
    
        cout << "-------------------\n";
    
        Multi<int, 9> multi(B<int>{});
        multi.set(5, C<int>{});
        multi.set(6, D<int>{});
        multi.do_something();
    }