std::is_base_of for template classes
Asked Answered
V

4

31

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?

Vicegerent answered 8/1, 2016 at 8:23 Comment(5)
'A' is not a type. You cant do that, but you can do 'std::is_base_of<A<int, int>, B<int>>'Stepup
Kind of similar question I answered some time back. #34670875Stepup
Would my_is_base_of<A, B<int>, char> be a reasonable usage for you?Quiet
@Quiet thanks. It would be very specific to the way B specializes A though. I am looking for a more general solution, if possible.Vicegerent
@Stepup thanks. I see, that could work in my setting.Vicegerent
A
32

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

Afflatus answered 8/1, 2016 at 8:44 Comment(16)
Huh, I had no idea that Ts... could be deduced in that context. Nice!Quiet
I arrived at the same solution, but this solution also fails (rather than returns false) with single non-public inheritance from A :(Secretin
@Jarod42: Thanks! That's pretty clever.Vicegerent
It seems you don't need the typename T in is_base_of_template_impl?Oxtail
@davidhigh: Indeed, simplified.Afflatus
Why does this fail with multiple inheritance?Senegambia
@Justin: In case you inherit from both C<int> and C<float>.Afflatus
Unfortunately this breaks in visual studio with more than 1 template parameter unless the second parameter is varadic... - rextester.com/CSMZ66843Imaginary
Well, the VS bug is fixed.Underwaist
It doesn't work when the template arguments contain values, anyone any ideas?Auliffe
@Sprite: we unfortunately don't have syntax to handle generically between type and non-type template parameters. You have to create similar traits to handle types as std::array.Afflatus
@Afflatus I am an engineer at Google interested in using the code you wrote here. If you are comfortable with permitting this, could you please release your snippet under a recognized open source license (opensource.org/licenses) by adding a comment clarifying which specific license terms apply to this snippet of code? Thanks for your help.Pesach
@JackHumphries: Just added in my profile the fact that original code I post on SO are under MIT license.Afflatus
@Afflatus Thank you!Pesach
"is_base_of_template<A,B>" sounds like this would be true if A was a base of the template B (which would be in line with std::is_base_of), but judging from the demo, it's the other way around, right? is_base_of_template<A,B> is true_type if A is derived from (any instanciation of) the template B, right?Ribonuclease
It is in the right direction, we check that B inherits from A<Xs...> Demo.Afflatus
P
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>> );
Pantheas answered 24/8, 2020 at 14:15 Comment(1)
Thanks. This is neat!Vicegerent
R
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

Roseliaroselin answered 7/3, 2018 at 23:47 Comment(1)
Your solution is quite good but it has a minor bug try to test static_assert(is_base_template_of<std::variant, std::variant<std::string>>::value);Sucker
S
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));
};
Sucker answered 7/5, 2018 at 10:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.