I have a set of classes that are chained using a member typedef Next
, as follows:
class Y; class Z;
class X { public: typedef Y Next; };
class Y { public: typedef Z Next; };
class Z { };
I need a way to get the final class of a chain, starting from any class of the chain. Thanks to the accepted answer of this post, I wrote the following code:
// cond_type<Condition, Then, Else>::type // selects type 'Then' if 'Condition' is true, or type 'Else' otherwise
template <bool Condition, typename Then, typename Else = void>
struct cond_type
{
typedef Then type;
};
template <typename Then, typename Else>
struct cond_type<false, Then, Else >
{
typedef Else type;
};
template <class C, typename _ = void>
struct chain
{
typedef C last;
};
template <class C>
struct chain<C, typename cond_type<false, typename C::Next>::type>
{
typedef typename chain<typename C::Next>::last last;
};
Using the above template chain<C>::last
, the following code properly instantiates 3 objects of class Z
, as expected:
chain<X>::last z1;
chain<Y>::last z2;
chain<Z>::last z3;
However, if the considered set of classes form an inheritance hierarchy, in the following way:
class U; class V;
class T { public: typedef U Next; };
class U : public T { public: typedef V Next; };
class V : public U { };
Then, using template chain<C>::last
, with any class C
of the above set, for example:
chain<T>::last v;
result in the following compile error:
1>test.cpp(88): error C3646: 'last' : unknown override specifier
I understand that the problem is that class V
inherits from typedef V Next
defined in the parent class U
, resulting in compilation of the specialized form of the template chain<V,V>
while the generic one should be used instead as V
has no member Next
.
Anyway, I am stuck here, as I need a mechanism that works, even in this case of a class hierarchy.
How could I do this ?
PS: inheritance between classes must remain public; member typedefs must remain public.
In the inheritance hierarchy, classes become more specific and concrete with each new subclass. If you move from a subclass back up to a superclass, the classes become more general and less specific. Class design should ensure that a superclass contains common features of its subclasses.
OOPs support the six different types of inheritance as given below : Single inheritance. Multi-level inheritance.
The type of inheritance in which more than one derived class inherits the properties of the same base class is called hierarchical inheritance. There are multiple child classes and a single parent class.
C++ has no direct method to check one object is an instance of some class type or not. In Java, we can get this kind of facility. In C++11, we can find one item called is_base_of<Base, T>. This will check if the given class is a base of the given object or not.
It's as simple as:
template <typename T, typename = void> struct last_t_impl
{
using type = T;
};
template <typename T> struct last_t_impl
<T, std::enable_if_t<!std::is_same_v<typename T::Next, T>>>
{
using type = typename last_t_impl<typename T::Next>::type;
};
template <typename T> using last_t = typename last_t_impl<T>::type;
Usage:
last_t<T> v1;
last_t<U> v2;
last_t<V> v3;
If you need the code above to compile for C++14 (instead of C++17), change std::is_same_v<A,B>
to std::is_same<A,B>::value
.
Note that your typename cond_type<false, T>::type
can be replaced with std::void_t<T>
(or std::conditional_t<false,T,void>
in C++14). But in this case it's not needed, since an end of a chain will be SFINAE-detected by std::is_same_v<typename T::Next, T>
. (Even if T::Next
doesn't exist for some reason, the SFINAE will still kick in and last_t<T>
will be just T
.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With