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.
CodePudding user response:
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
CodePudding user response:
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.
#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>);
CodePudding user response:
You could make it a type trait:
#include <utility> // declval
template<class L>
struct Leaf {
static L test(...); // match L's without Def, terminating match
template<class M> // match L's with Def, recursively
static auto test(const M&) -> typename Leaf<typename M::Def>::type;
// find most specialized matching test() overload:
using type = decltype( test(std::declval<L>()) );
};
#if __cplusplus >= 201703L
template<class L> // helper variable template
using Leaf_t = typename Leaf<L>::type;
#endif
Demo and template resolution @ cppinsights
Alternative version if test(...)
doesn't appeal to you:
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;
using type = decltype( test(std::declval<L>()) );
};