Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trait to check if some specialization of template class is base class of specific class

There is std::is_base_of in modern STL. It allow us to determine whether the second parameter is derived from first parameter or if they are the same classes both or, otherwise, to determine is there no such relation between them.

Is it possible to determine whether the one class is derived from some concrete template class without distinction of which concrete actual parameters involved to its specialization?

Say, we have;

template< typename ...types >
struct B {};

And

template< typename ...types >
struct D : B< types... > {};

Is it possible to define a type trait:

template< typename T > is_derived_from_B;

Such that it is derived from std::true_type when T is any specialization of D and derived from std::false_type if T is not derived from any specialization of B?

like image 705
Tomilov Anatoliy Avatar asked Sep 15 '14 10:09

Tomilov Anatoliy


People also ask

What is the difference between class template and template class?

An individual class defines how a group of objects can be constructed, while a class template defines how a group of classes can be generated. Note the distinction between the terms class template and template class: Class template. is a template used to generate template classes.

Can a template base class derived?

Inheriting from a template classIt is possible to inherit from a template class. All the usual rules for inheritance and polymorphism apply. If we want the new, derived class to be generic it should also be a template class; and pass its template parameter along to the base class.

What are trait classes?

Definition of Class Trait (noun) A behavior, custom, or norm–either real or imagined–that define or reflect a class.

What is meant by template specialization in C++?

The act of creating a new definition of a function, class, or member of a class from a template declaration and one or more template arguments is called template instantiation. The definition created from a template instantiation is called a specialization.


2 Answers

If you can assume that a derived type uses a public inheritance from B<Args...> (and so the upcasting is possible), then you can use the following SFINAE:

namespace detail
{
    template <typename Derived>
    struct is_derived_from_B
    {
        using U = typename std::remove_cv<
                                  typename std::remove_reference<Derived>::type
                                >::type;

        template <typename... Args>
        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*>()));
    };
}

template <typename Derived>
using is_derived_from_B = typename detail::is_derived_from_B<Derived>::type;

Tests:

static_assert(is_derived_from_B<const D<int, char, float>>::value, "!");
static_assert(!is_derived_from_B<int>::value, "!");
static_assert(!is_derived_from_B<B<int,int>>::value, "!");
static_assert(!is_derived_from_B<std::vector<int>>::value, "!");

DEMO 1

It can be generalized to accept any base class template:

namespace detail
{
    template <template <typename...> class Base, typename Derived>
    struct is_derived_from_template
    {
        using U = typename std::remove_cv<
                                  typename std::remove_reference<Derived>::type
                                >::type;

        template <typename... Args>
        static auto test(Base<Args...>*)
            -> typename std::integral_constant<bool
                                          , !std::is_same<U, Base<Args...>>::value>;

        static std::false_type test(void*);

        using type = decltype(test(std::declval<U*>()));
    };
}

template <template <typename...> class Base, typename Derived>
using is_derived_from_template
                = typename detail::is_derived_from_template<Base, Derived>::type;

Tests:

static_assert(is_derived_from_template<B, const D<int, int>>::value, "!");
static_assert(!is_derived_from_template<B, int>::value, "!");
static_assert(!is_derived_from_template<B, B<int, int>>::value, "!");
static_assert(!is_derived_from_template<B, std::vector<int>>::value, "!");

DEMO 2

like image 130
Piotr Skotnicki Avatar answered Oct 23 '22 18:10

Piotr Skotnicki


I want to propose another solution, that will also work in case of private inheritance. Downsides are that it requires you to modify the base class template and it is base class-specific.

Assuming your base class is template< typename... Args > class Base, you need to add a friend function to it:

template< /*whatever*/ > class Base {
  //...

  template< typename T >
  friend std::enable_if_t<
    std::is_base_of<Base, T>::value
  > is_derived_from_Base_impl(T const&); //unevaluated-only
};

Then, you can write your trait:

template< typename T, typename Enable=void >
struct is_derived_from_Base : std::false_type { };

template< typename T >
struct is_derived_from_Base<T,
  decltype(is_derived_from_Base_impl(std::declval<T const&>()))
> : std::true_type { };

This trait won't work in Visual Studio 2015 prior to Update 1, you'll have to write something like:

namespace is_derived_from_Base_adl_barrier {
  struct no{}; //anything but void
  no is_derived_from_Base_impl(...);
  template< typename T >
  struct is_derived_from_Base : std::is_void<decltype(
    is_derived_from_Base_impl(std::declval<T const&>());
  )> { };
}
using is_derived_from_Base_adl_barrier::is_derived_from_Base;

The thing works because argument-name dependent lookup will find the friend function despite the private inheritance, and the friend function (or functions, if multiple are found) will then check is_base_of on an actual specialization.

like image 29
Giulio Franco Avatar answered Oct 23 '22 19:10

Giulio Franco