Let's consider the following CRTP code, with the slight modification that the derived class has to be a template one:
template<template<typename> class DERIVED, typename T>
struct crtp_base {};
template<typename T>
struct derived : crtp_base<derived,T> {};
I would like to define a type trait telling whether a class is derived from the CRTP base class. In other words, I need a type trait that works on a class template (and not on a type).
The first approach would be to define a generic version of the trait, with the peculiarity that the parameter is a template template one:
template<template<typename> class DERIVED>
struct is_crtp_based : std::false_type {};
Now, a first attempt for the template specialization of interest:
template<template<typename> class DERIVED, typename T>
struct is_crtp_based <crtp_base<DERIVED,T>> : std::true_type {};
but the compiler is not happy with this since it expects a class template but it finds a (non template) class crtp_base<DERIVED,T>
instead.
Question: how should one write the specialization in such a case ? More generally, how to deal with type traits that take a class template as parameter ? I guess that there are already existing use cases like this but I don't find any of them right now.
Note: I can solve the question by the following trait that uses std::is_base_of
:
template<template<typename> class DERIVED, typename T=void>
struct is_crtp_based : std::integral_constant<bool, std::is_base_of_v<crtp_base<DERIVED,T>,DERIVED<T> >> {};
static_assert (is_crtp_based<derived>::value == true);
but I would like to understand how to correct my first incorrect attempt through template specialization.
UPDATE: For a little bit more context, the crtp_base
needs to know the T
type, which explains the T
template parameter in its definition and makes all the remaining more difficult (see my comment to 463035818_is_not_an_ai's answer).
I dare to claim that what you actually want is this:
template<typename DERIVED>
struct crtp_base {};
template<typename T>
struct derived : crtp_base<derived<T>> {};
To check whether a type derives from an instantiation of crtp_base
you can use
template <typename T>
using is_crtp = std::is_base_of<crtp_base<T>,T>;
And thats it.
No need for complicated template template arguments.
You want T
to be available in the crtp base? Ok, write a trait for that:
template <typename T>
struct get_T;
template <template <typename> class X,typename T>
struct get_T<X<T>> {
using type = T;
};
Now get_T< derived<T>>::type
is T
. In your base you can use get_T<DERIVED>
. There is a template template argument, but only in one place, not all over your code.
Note that the get_T
is rather limited, it only works for templates with a single type argument. Though tahts exactly the same limitation as in your version of crtp_base
.