29

Is there a way to test std::is_base_of<A, B> when A is a template class?

template <typename X, typename Y> class A {};

template <typename X> class B : public A<X, char> {};

I want to statically test something like, std::is_base_of<A, B<int>> meaning, B is derived from any specialization of A. (To make it more general, let's say we don't know the way B specializes A, i.e. B<X> derives from A<X, char>)

One way to solve would be to derived A from a (non-template) class say C, and then check std::is_base_of<C, B<int>>. But is there another way to do this?

Niall
  • 30,036
  • 10
  • 99
  • 142
Saswat Padhi
  • 6,044
  • 4
  • 20
  • 25

4 Answers4

29

You may do the following:

template <template <typename...> class C, typename...Ts>
std::true_type is_base_of_template_impl(const C<Ts...>*);

template <template <typename...> class C>
std::false_type is_base_of_template_impl(...);

template <typename T, template <typename...> class C>
using is_base_of_template = decltype(is_base_of_template_impl<C>(std::declval<T*>()));

Live Demo

but will fail with multiple inheritance or private inheritance from A.

With Visual Studio 2017 this will fail when the base class template has more than one template parameter, and it is unable to deduce Ts...

Demo

VS Bug Report

Refactoring solves the problem for VS.

template < template <typename...> class base,typename derived>
struct is_base_of_template_impl
{
    template<typename... Ts>
    static constexpr std::true_type  test(const base<Ts...> *);
    static constexpr std::false_type test(...);
    using type = decltype(test(std::declval<derived*>()));
};

template < template <typename...> class base,typename derived>
using is_base_of_template = typename is_base_of_template_impl<base,derived>::type;

Live Demo

rmawatson
  • 1,909
  • 12
  • 20
Jarod42
  • 203,559
  • 14
  • 181
  • 302
2

Bit late to the party here but I wanted to give a variant of the above

template < template <typename...> class Base,typename Derived>
struct is_base_of_template
{
    // A function which can only be called by something convertible to a Base<Ts...>*
    // We return a std::variant here as a way of "returning" a parameter pack
    template<typename... Ts> static constexpr std::variant<Ts...> is_callable( Base<Ts...>* );

    // Detector, will return type of calling is_callable, or it won't compile if that can't be done
    template <typename T> using is_callable_t = decltype( is_callable( std::declval<T*>() ) );

    // Is it possible to call is_callable which the Derived type
    static inline constexpr bool value = std::experimental::is_detected_v<is_callable_t,Derived>;

    // If it is possible to call is_callable with the Derived type what would it return, if not type is a void
    using type = std::experimental::detected_or_t<void,is_callable_t,Derived>;
};

template < template <typename...> class Base,typename Derived> 
using is_base_of_template_t = typename is_base_of_template<Base,Derived>::type;

template < template <typename...> class Base,typename Derived>
inline constexpr bool is_base_of_template_v = is_base_of_template<Base,Derived>::value;

This uses the proposed is_detected machanism which I think makes the intent of the test a bit clearer. However I can now get the type(s) with which the base class is instantiated at the same time which I find useful. So I can write

template <typename T, typename U> struct Foo { };

struct Bar : Foo<int,std::string> { };

static_assert( is_base_of_template_v<Foo,Bar> );

// The variant type contains the types with which the Foo base is instantiated 
static_assert( std::is_same_v<std::variant<int,std::string>,is_base_of_template_t<Foo,Bar>> );
goneskiing
  • 1,659
  • 1
  • 13
  • 22
1

The following solution works with protected inheritance.

template <template <typename...> class BaseTemplate, typename Derived, typename TCheck = void>
struct test_base_template;

template <template <typename...> class BaseTemplate, typename Derived>
using is_base_template_of = typename test_base_template<BaseTemplate, Derived>::is_base;

//Derive - is a class. Let inherit from Derive, so it can cast to its protected parents
template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived
{
    template<typename...T>
    static constexpr std::true_type test(BaseTemplate<T...> *);
    static constexpr std::false_type test(...);
    using is_base = decltype(test((test_base_template *) nullptr));
};

//Derive - is not a class, so it is always false_type
template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<!std::is_class_v<Derived>>>
{
    using is_base = std::false_type;
};

Surprisingly, on VS2017, it works with multiple inheritance from the same template, like C< int > and C< float > both. (have no idea how!)

Check the Link to test code

jenkas
  • 872
  • 14
  • 16
1

Based on Evgeny Mamontov answer I believe the proper solution is

template <template <typename...> class BaseTemplate, typename Derived>
struct test_base_template<BaseTemplate, Derived, std::enable_if_t<std::is_class_v<Derived>>> : Derived
{
    template<typename...T>
    static constexpr std::true_type test(BaseTemplate<T...> *);
    static constexpr std::false_type test(...);
    using is_base = decltype(test((Derived *) nullptr));
};
Anton Dyachenko
  • 169
  • 1
  • 9