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;
};
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();
}