3

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.

The_Sympathizer
  • 1,191
  • 9
  • 17
  • you're looking for `std::is_detected<>` – lorro Jul 05 '22 at 20:44
  • @lorro: I am not sure if I even have that. It looks like it's still called "experimental" and suggests it's not even fully mature in C++20 (and GCC support for C++20 I've not tested but heard is still wanting.). – The_Sympathizer Jul 05 '22 at 20:47
  • Can be done without, likely I can show you an example of it. – lorro Jul 05 '22 at 20:53
  • in your example, `HasDef::Def` is both private and inaccessible from descendant. Which is more important: that it descends from a common template, or that you have a `type` in the class? – lorro Jul 05 '22 at 21:08
  • @lorro: that was a mistake, actually. fixed. – The_Sympathizer Jul 05 '22 at 21:09
  • @The_Sympathizer I assumed `public` inheritance when I wrote my answer. You still haven't changed that. – Ted Lyngmo Jul 05 '22 at 21:42
  • @Ted Lyngmo : Thanks for that catch too. Yes, you're right. You assumed correctly. – The_Sympathizer Jul 06 '22 at 00:17
  • Possible duplicate of [How to detect whether there is a specific member variable in class?](https://stackoverflow.com/questions/1005476/) – Remy Lebeau Jul 06 '22 at 01:49
  • @Remy Lebeau : The problem was not the detection, but the recursion. – The_Sympathizer Jul 06 '22 at 01:54
  • @The_Sympathizer The recursion is the easy part. You need a detection in order to decide whether to continue or end the recursion... – Remy Lebeau Jul 06 '22 at 01:55
  • @Remy Lebeau : Yes, but my compiler was giving, in one version of the code - that was _not_ the one I gave in the post, apparently, but the one integrated and enmeshed with my main program - some errors about that the struct "ending with the closing brace" because I referenced the included type "type" in a self-referencing typedef to define said type. Detection was already a done deal because I had a class hierarchy similar to the "HasDef<>" thing there for the state machine itself but that distinguishes a leaf from an inner state, and only inner states can have defaults, and – The_Sympathizer Jul 06 '22 at 02:21
  • thus ``std::is_base_of`` sufficed. – The_Sympathizer Jul 06 '22 at 02:21

3 Answers3

3

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

alagner
  • 3,448
  • 1
  • 13
  • 25
2

You can implement this with SFINAE to separate types that have the typedef from ones that don't when trying to follow them to their leaf. I used the trick from this SO answer here.

The first implementation for Leaf follows the typedef recursively, the second one just defines the type itself as there is no typedef to follow.

Also note I changed your classes to struct for default-public inheritance. Also I changed the order of their definitions for this to compile.

Compiler explorer

#include <type_traits>

namespace detail
{
    template<typename T> struct contains_def {
        template<typename U> static char (&test(typename U::Def const*))[1];
        template<typename U> static char (&test(...))[2];
        static const bool value = (sizeof(test<T>(0)) == 1);
    };
    
    template<typename T, bool has = contains_def<T>::value>
    struct Leaf {
        using type = typename Leaf<typename T::Def>::type;
    };

    template<typename T>
    struct Leaf<T, false> {
        using type = T;
    };
}

template <typename T>
using Leaf = detail::Leaf<T>; // expose Leaf to the global scope

template <typename T>
using Leaf_t = typename Leaf<T>::type; // for convenience


template<typename T>
struct AddDef {
    using Def = T;
};


struct C {};
struct B : AddDef<C> {};
struct A : AddDef<B> {};

static_assert(std::is_same_v<Leaf_t<A>, C>);
static_assert(std::is_same_v<Leaf_t<B>, C>);
static_assert(std::is_same_v<Leaf_t<C>, C>);
perivesta
  • 3,417
  • 1
  • 10
  • 25
2

You could make it a type trait:

#include <utility> // declval

template<class L>
struct Leaf {
    template<class M>
    static L test(const M&); // match L's without Def

    template<class M>        // match L's with Def
    static auto test(M&&) -> typename Leaf<typename M::Def>::type;

    // find matching test() overload, prefer test(M&&):
    using type = decltype( test(std::declval<L>()) );
};

template<class L> // bonus helper types
using Leaf_t = typename Leaf<L>::type;

Demo and template resolution @ cppinsights

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108