Check if the class explicitly defines the type of element in the inheritance hierarchy

I have a set of classes that are chained using the typedef Next member, as shown below:

 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 chain class starting from any chain class. 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 correctly creates three 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 forms an inheritance hierarchy, as follows:

 class U; class V; class T { public: typedef U Next; }; class U : public T { public: typedef V Next; }; class V : public U { }; 

Then, using the template chain<C>::last , with any C class of the above set, for example:

 chain<T>::last v; 

will result in the following compilation error:

 1>test.cpp(88): error C3646: 'last' : unknown override specifier 

I understand that the problem is that the class V inherits from the typedef V Next defined in the parent class U , which leads to compilation of the specialized form of the template chain<V,V> , while the general one should be used instead as V does not have member of Next .

Anyway, I'm stuck here, as I need a mechanism that works, even in this case the class hierarchy.

How can i do this?

PS: inheritance between classes should remain open; member typedefs should remain open.

+5
source share
2 answers

It's simple:

 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; 

Using:

 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 .


Please 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 is not needed, since the end of the chain will be SFINAE-detected std::is_same_v<typename T::Next, T> . (Even if T::Next for some reason does not exist, SFINAE will still be pressed, and last_t<T> will just be T )

+6
source

Classes with whole chains must be implemented as a separate abstraction from the 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 cannot detect a chain loop, but this implementation saves two abstractions (class hierarchy and type chain) as separate concepts, so you can provide such functions without changing your hierarchical class, which is better in my view.

0
source

Source: https://habr.com/ru/post/1274552/


All Articles