Search code examples
c++template-specializationtype-traits

How to write a template specialization of a type trait that works on a class template?


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).


Solution

  • 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.