5

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.

shrike
  • 4,449
  • 2
  • 22
  • 38
  • 2
    If you are in c++11 or newest, you don't have to define your `cond_type` : see http://fr.cppreference.com/w/cpp/types/conditional – Guillaume Fouillet Jan 07 '18 at 11:56
  • You should consider separate the inheritance hierarchy and the chain abstraction, by the way : it would simplify implementation and will be simpler to maintain, even it will need more classes. – Guillaume Fouillet Jan 07 '18 at 12:50

2 Answers2

7

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.)

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
  • 1
    I was looking for something using std::is_same<> but could not get it. Your solution works great and is far more concise than mine. Thank you very much. – shrike Jan 07 '18 at 13:24
0

The type-chained classes should be implemented as a separate abstraction from your class hierarchy.

#include <iostream>
#include <string>
#include <type_traits>

// Definitions for TypeChain as an abstraction

// Simple interface to declare next class for a class.
template <typename N>
struct Next {
    using next = N;
};

// Primary template for a type chain.
template <typename T>
struct TypeChain : Next<void>{};


/// Implementation of type-function last.
template <typename T, typename C = typename TypeChain<T>::next> struct last_t_impl
{
    using type = std::conditional_t<std::is_same_v<C, void>, T, typename last_t_impl<C>::type>;
};

// Never used, but needed to end recursion.
template <> struct last_t_impl<void, void>
{
    using type = void;
};

template <typename T> using last_t = typename last_t_impl<T>::type;

// Définition of the class hierarchy, without chaining.
class T             { };
class U : public T  { };
class V : public U  { };

// Definition of the chain.
// T => U => V

// Specialisation of TypeChain for T
template<>
struct TypeChain<T> : Next<U> {};

// Specialisation of TypeChain for U
template<> 
struct TypeChain<U> : Next<V>{};

// No specialisation for V, since it has no next value.


// Test (should run three 1)
int main()
{
    std::cout << std::is_same_v<last_t<T>,  V> << "\n";
    std::cout << std::is_same_v<last_t<U>,  V> << "\n";
    std::cout << std::is_same_v<last_t<V>,  V> << "\n";
}

It can't detect a chain loop, but this implementation keep the two abstraction (class hierarchy and type chain) as separate concepts, so you can provide those kind of functionnalities without modifiying your class hierachy, which is better, in my view.

  • The example I provided is a simplification of the real code. I actually need to make the type-chain explicitely visible in each class. Anyway, thanks for your suggestion, it could be usefull for some other purpose. – shrike Jan 08 '18 at 13:55