Search code examples
c++templatesc++11sfinaetype-traits

Technique to distinguish between ordinary template parameters and template template parameters using SFINAE resolution


The question inspired by recently arised question about extended std::is_base_of type trait.

Is there any technique, which allows us to distinguish between ordinary template parameter and template template parameter in modern C++ or its extensions (say, -std=gnu++1z clang++/g++)?

namespace details
{

template< /* ??? */ base >
struct is_derived_from;

template< typaneme base >
struct is_derived_from< base >
{
    static std::true_type test(base *);
    static std::false_type test(void *);
};

template< template< typename ...formal > base >
struct is_derived_from< /* ??? */ >
{
    template< typename ...actual > // actual parameters must be here!
    static std::true_type test(base< actual... > *);
    static std::false_type test(void *);
};

} // namespace details

template< typename derived, /* ??? */ base >
using is_derived_from = decltype(details::is_derived_from< /* ? base< ? > */ >::test(std::declval< typename std::remove_cv< derived >::type * >()));

In positive case it allows us to make some of useful type traits much more powerfull (for example, STL's std::is_base_of).

I think it requires a language feature as a "generalized typenames", isn't it?


Solution

  • There can be only one set of template parameters for class templates, but you can use overloading constexpr function templates instead that dispatches to the appropriate class template. Take the is_derived_from trait in the linked question, with an extra SFINAE parameter so that you don't get a hard error when B is an inaccessible or ambiguous base:

    #include <type_traits>
    namespace detail
    {
        template <template <class...> class B, typename Derived>
        struct is_derived_from
        {
            using U = typename std::remove_cv<Derived>::type;
    
            template <typename... Args, 
                      typename = std::enable_if_t<
                                 std::is_convertible<U*, Base<Args...>*>::value>>
            static auto test(B<Args...>*)
                -> typename std::integral_constant<bool
                                               , !std::is_same<U, B<Args...>>::value>;
    
            static std::false_type test(void*);
    
            using type = decltype(test(std::declval<U*>()));
        };
    
        using std::is_base_of; // may want to use is_convertible instead to match
                               // the semantics of is_derived_from
    }
    
    template <template <class...> class B, typename Derived>
    constexpr bool my_is_base_of() { return detail::is_derived_from<B, Derived>::type::value; }
    
    template <class B, typename Derived>
    constexpr bool my_is_base_of() { return detail::is_base_of<B,Derived>::value; }
    
    struct B {};
    struct D : B {};
    
    template<class ...>
    struct B2 {}; 
    struct D2 : B2<int, double> { };
    
    int main() {
      static_assert(my_is_base_of<B2, D2>(), "Oops");
      static_assert(my_is_base_of<B, D>(), "Oops");
      static_assert(my_is_base_of<B2<int, double>, D2>(), "Oops");
      static_assert(!my_is_base_of<B, D2>(), "Oops");
    }
    

    Demo.