Reputation: 898
I have the following design problem, some manager class has a function that is supposed to allow classes that extend a specific set of classes and add them to a container that keeps the functionality of that specific set of classes. So we have:
class Base_1 {};
class Base_2 {};
class A :
public Base_1,
public Base_2
{
};
class Container
{
template<typename T> // where T should extend Base_1 and Base_2
void Add(T* pObj) {
elements.push_back(pObj);
}
std::vector<???> elements; // all elements should extend Base_1 and Base_2
};
Something similar is possible in C# with interfaces but I cant find any way to solve this in c++.
I do not want to create an intermediate class Base_1_2 because that stops working with 3 base classes where some manager needs its elements to have base classes 1 and 2, and another manager needs 2 and 3.
So my question is, how do u design this?
EDIT: In response to: "This smells like an XY problem" – cdhowie
I can imagine that's the case. So I'll try to explain the 'X' problem.
I have a set of classes that share a lot of functionality. Something like "load to gpu" "render" "release gpu memory" "update". But not all functionality is applicable to all classes. Some might not have the "render" for example. Now i have a class that needs to store a lot of objects and check if "load to gpu" has happened yet before calling "render". But how to implement this? How can u store classes based on this combination of functionality (I might be going towards the 'Y' a bit here again).
Edit 2:
Similar (but not generic) to the alternative composed by cdhowie, the following would be a solution too? (maybe leaving out the is_base_of on the Add function)
class MyWrapper
{
public:
template<class T, typename = std::enable_if_t<
std::is_base_of<Base_1, T>::value && std::is_base_of<Base_2, T>::value>>
static MyWrapper MakeWrapper(T* pObj)
{
return MyWrapper(dynamic_cast<Base_1*>(pObj), dynamic_cast<Base_2*>(pObj));
}
Base_1* as_base_1;
Base_2* as_base_2;
private:
MyWrapper(Base_1* p1, Base_2* p2) {
as_base_1 = p1;
as_base_2 = p2;
}
};
class Container
{
public:
template<class T>
void Add(T* pObj) {
elements.push_back(MyWrapper::MakeWrapper<T>(pObj));
}
void Update() {
for (auto& e : elements) {
e.as_base_1->do_1();
e.as_base_2->do_2();
}
}
std::vector<MyWrapper> elements; // all elements should extend Base_1 and Base_2
};
void test() {
A* pA = nullptr;
Container c;
c.Add(pA);
}
Upvotes: 0
Views: 1294
Reputation: 169143
You can disable Add
when T
isn't derived from both types:
template<typename T>
typename std::enable_if<
std::is_base_of<Base_1, T>::value &&
std::is_base_of<Base_2, T>::value,
void
>::type Add(T* pObj) {
elements.push_back(pObj);
}
Then the type of pointer you store in the vector doesn't make much difference, if Base_1
and Base_2
are polymorphic (just add a virtual destructor to both) -- then you can dynamic_cast
to whatever type you need. So you could store Base_1 *
or Base_2 *
.
You could also have two vectors: one to store Base_1
pointers and one to store Base_2
pointers, and this wouldn't require either type to be polymorphic.
However, this seems like a particularly strange problem to solve and you might consider taking a step back from your design to see if there is a better alternative solution.
I do not want to create an intermediate class Base_1_2 because that stops working with 3 base classes where some manager needs its elements to have base classes 1 and 2, and another manager needs 2 and 3.
This is untrue. Just use virtual inheritance in the intermediate classes.
As an alternative, you can write a type that will store pointers to all of the interfaces you require. For example:
#include <type_traits>
#include <tuple>
template <typename, typename...>
struct is_subtype_of_all : std::true_type {};
template <typename Derived, typename Base, typename... BaseTail>
struct is_subtype_of_all<Derived, Base, BaseTail...> : std::conditional<
std::is_base_of<Base, Derived>::value,
is_subtype_of_all<Derived, BaseTail...>,
std::false_type
>::type {};
template <typename...>
struct type_index;
template <typename T, typename... Tail>
struct type_index<T, T, Tail...> : std::integral_constant<std::size_t, 0> {};
template <typename T, typename U, typename... Tail>
struct type_index<T, U, Tail...> : std::integral_constant<std::size_t, 1 + type_index<T, Tail...>::value> {};
template <typename... T>
class interface_wrapper
{
public:
template <typename U, typename = typename std::enable_if<is_subtype_of_all<U, T...>::value>::type>
explicit interface_wrapper(U *p)
: pointers{static_cast<T *>(p)...} {}
template <typename U>
U * get() const
{
return std::get<type_index<U, T...>::value>(pointers);
}
private:
std::tuple<T *...> pointers;
};
Now, you can store interface_wrapper<Base_1, Base_2>
in your vector, and you can use .get<Base_1>()->some_member_of_Base_1
on the wrapper objects.
This implementation does not require that the object be polymorphic.
Upvotes: 4