Reputation: 1743
I have two set of classes, say set A = {a1, a2, a3}
all derived from Asuper
, and B = {b1, b2}
all derived from Bsuper
, and only the classes containing A and B can be used together (for eg. a1 and b2, or a3 and b1; they have logical dependencies). How can I communicate this to the users through a proper class design?
One way is to have enums associating with each classes and present them somewhere. Eg:
class LogicalGrouping{
enum gr1{A1, A2, A3};
enum gr2{B1, B2};
Asuper *a;
Bsuper *b;
setA(Asuper *a); //user can now manually check the types in gr1 and assign
setB(Bsuper *b);
};
But in my obvious opinion this is super ugly. One has to do manual decoding of the meaning of enums and assign them. I would have like to have something like a list of classes like classlist {a1, a2, a3};
Is there a cleaner way of doing it?
Upvotes: 0
Views: 55
Reputation: 4076
This took a long time, but this is the code that I came up with, based on @TartanLlama's suggestion (more or less):
#include <iostream>
#include <cassert>
#include <type_traits>
#include <typeinfo>
#include <string>
//--- The class hierarchy
struct A{ virtual std::string name() const{ return "A";} };
struct B{ virtual std::string name() const{ return "B";} };
template <int i> struct An : A
{
virtual std::string name() const{ return A::name() + std::to_string(i);}
};
template <int i> struct Bn : B
{
virtual std::string name() const{ return B::name() + std::to_string(i);}
};
template <int i> struct AnBn : An<i>, Bn<i>
{
virtual std::string name() const{ return An<i>::name() + Bn<i>::name(); }
};
template <int i,int j> struct AnBm : An<i>, Bn<j>
{
virtual std::string name() const{ return An<i>::name() + Bn<j>::name(); }
};
//--- End of class hierarchy
//--- The magic that determines whether A/B is base of
template <class T> using A_is_base_of =
std::is_base_of<A,typename std::remove_reference<T>::type>;
template <class T> using B_is_base_of =
std::is_base_of<B,typename std::remove_reference<T>::type>;
template <class T>
struct AB_is_base_of
{
const static bool value = A_is_base_of<T>::value && B_is_base_of<T>::value;
typedef typename std::integral_constant<bool,value>::type type;
};
//--- A tester - requires T has member function name... just for now...
struct IsAB_BaseOf
{
template <class T>
static void test(T&& value)
{
return test(std::forward<T>(value),
typename AB_is_base_of<T>::type());
}
private: template <class T>
static void test(T&& value, std::true_type)
{
std::cout << value.name() << " derives from both A and B" << std::endl;
}
private: template <class T>
static void test(T&& value, std::false_type)
{
std::cout << value.name() << " does not derive from both A and B" << std::endl;
}
};
template <class T>
bool ab_IsBaseOf( T&& type )
{
return AB_is_base_of<T>::value;
}
int main() {
assert(!ab_IsBaseOf(An<0>()));
assert(!ab_IsBaseOf(Bn<0>()));
assert(!ab_IsBaseOf(Bn<0>()));
assert(!ab_IsBaseOf(An<0>()));
assert(!ab_IsBaseOf(An<0>()));
assert(!ab_IsBaseOf(Bn<0>()));
assert(ab_IsBaseOf(AnBn<0>()));
assert(ab_IsBaseOf(AnBm<0,1>()));
IsAB_BaseOf::test(AnBm<0,1>());
IsAB_BaseOf::test(An<0>());
IsAB_BaseOf::test(Bn<0>());
}
Output:
A0B1 derives from both A and B
A0 does not derive from both A and B
B0 does not derive from both A and B
Basically:
The idea is that ab_IsBaseOf return true if A and B is the base of T.
EDIT:
One could with little effort create a type from the meta function, which you could use as argument to a function that would be called only if T is of the correct type (known as tag dispatching).
EDIT:
I've modified the presented source now to use tag dispatching select a function based on whether type T derives from A and B or not, which is what the OP wanted according to my understanding. Questions are welcome.
Upvotes: 1
Reputation: 36102
Create two interfaces IA and IB let all A implement IA and all B implement IB then create a third interface IAB that inherits from both IA and IB. This will ensure that you have one of each.
When the user of your class wants to use your classes he needs to inherit from IAB which forces him to pick one A and one B.
Upvotes: 1