Search code examples
c++templatestemplate-meta-programming

Can you "hop" between "linked classes" in C++ metaprogramming?


Suppose you have something like this:

template<class D>
class HasDef {
public:
     typedef D Def;
};

class A : public HasDef<class B> {};
class B : public HasDef<class C> {};
class C {};

So it is like a "metaprogramming linked list", with type links, via the included typedef Def. Now I want to make a template "Leaf" that, when applied to A, follows the links to yield C:

void f() {
     Leaf<A>::type v; // has type C
}

Is it even possible at all to do this? I've tried some methods with std::compare and similar, but none are valid code: everything seems to run into issues with either that C has no Def typedef, or else that the type Leaf<> itself is incomplete when the inner recursive "call" is made so it (or its internal type type) cannot be referenced.

FWIW, the reason I want this is for making a "hierarchical state machine" where that Def represents the default state for each state in the hierarchy, and something a bit more elaborate that this seems to provide a fairly neat and clean "user interface syntax" for it.


Solution

  • I don't really like f(...) in modern code, thus my version uses void_t from C++17:

    #include <type_traits>
    
    template<class D>
    struct HasDef {
    
         typedef D Def;
    };
    
    struct A : HasDef<class B> {};
    struct B : HasDef<class C> {};
    struct C {};
    
    
    template <typename T, typename=void>
    struct DefPresent : std::false_type{};
    
    template <typename T>
    struct DefPresent<T, std::void_t<typename T::Def>> : std::true_type{};
    
    
    template<typename T, bool deeper = DefPresent<T>::value>
    struct Leaf
    {
        using Type = typename Leaf<typename T::Def>::Type;
    };
    
    template<typename T>
    struct Leaf<T, false >
    {
        typedef T Type;
    };
    
    
    
    static_assert(std::is_same<typename Leaf<C>::Type, C>::value, "C");
    static_assert(std::is_same<typename Leaf<B>::Type, C>::value, "B");
    static_assert(std::is_same<typename Leaf<A>::Type, C>::value, "A");
    

    https://godbolt.org/z/5h5rfe81o

    EDIT: just for completenes, 2 C++20 variants utilizing concepts. Tested on GCC 10

    #include <type_traits>
    #include <concepts>
    
    template<class D>
    struct HasDef {
         typedef D Def;
    };
    
    struct A : HasDef<class B> {};
    struct B : HasDef<class C> {};
    struct C {};
    
    template <typename T>
    concept DefPresent = requires(T a)
    {
        typename T::Def;
    };
    
    template<typename T>
    struct Leaf
    {
        using Type = T;
    };
    
    
    template<typename T>
        requires DefPresent<T>
    struct Leaf<T>
    {
        using Type = Leaf<typename T::Def>::Type;
    };
    
    
    static_assert(std::is_same_v<typename Leaf<C>::Type, C>, "C");
    static_assert(std::is_same_v<typename Leaf<B>::Type, C>, "B");
    static_assert(std::is_same_v<typename Leaf<A>::Type, C>, "A");
    
    
    template<typename T>
    struct Leaf2
    {
        template <typename M>
        static M test(M&&);
    
        template <DefPresent M>
        static auto test(M&&) -> typename Leaf2<typename M::Def>::Type;
    
        using Type = decltype(test(std::declval<T>()));
    };
    
    static_assert(std::is_same<typename Leaf2<C>::Type, C>::value, "C");
    static_assert(std::is_same<typename Leaf2<B>::Type, C>::value, "B");
    static_assert(std::is_same<typename Leaf2<A>::Type, C>::value, "A");
    

    https://godbolt.org/z/vcqEaPrja