Reputation: 753
I have two template classes A and B
template <typename T> class A { /* ... */ };
template <typename T> class B { /* ... */ };
Now I would like to write a function that performs different actions, depending on which base classes are implemented in the passed argument.
template <typename T>
void foo(const T& object) {
if ( /* T inherits from some A<S1> */ )
std::cout << "object has type A";
if ( /* T inherits from some B<S2> */ )
std::cout << "object has type B";
}
I guess I could add S1
and S2
as template parameters to foo but specifying them manually for each call is a great hassle. In theory, if T
is known, the compiler should be able to check whether it inherits any A<S>
.
Can this be done?
edit:
In addition to the solution of StoryTeller, I am using this piece of code to cast my T
-objects accordingly:
template<template<typename...> class TT, class T>
struct specialization_base_of {
template<typename... Args>
static constexpr TT<Args...> checkVal(TT<Args...> const&);
static constexpr void checkVal(...);
template<typename... Args>
static constexpr TT<Args...>& checkRef(TT<Args...> const&);
static constexpr void checkRef(...);
template<typename... Args>
static constexpr TT<Args...> const& checkCref(TT<Args...> const&);
static constexpr void checkCref(...);
template<typename... Args>
static constexpr TT<Args...>* checkPtr(TT<Args...> const&);
static constexpr void checkPtr(...);
using value_type = decltype(checkVal(std::declval<T>()));
using ref_type = decltype(checkRef(std::declval<T>()));
using cref_type = decltype(checkCref(std::declval<T>()));
using ptr_type = decltype(checkPtr(std::declval<T>()));
};
Upvotes: 1
Views: 54
Reputation: 170045
The good old trick that relies on overload resolution should work here as well:
template<template<typename...> class TT, class T>
struct is_specialization_base_of {
template<typename... Args>
static constexpr std::true_type check(TT<Args...> const&);
static constexpr std::false_type check(...);
static constexpr bool value = decltype(check(std::declval<T>()))::value;
};
The first template parameter is template name, which can accept any number of type argument. You need one, but why limit ourselves?
We then define two overloads, one is a template that accepts any hypothetical specialization of TT
, and the second is a fallback that is a C-style variable argument function. This is the entire machinery right there. If we call check
with any class that publicly derives from a hypothetical specialization of TT
, the first overload is picked due to how overload resolution works. Otherwise the fallback is chosen.
The value of our trait is determined by the type produced in the unevaluated context of decltype
, there the overload resolution is performed, but since it's an unevaluated context nothing needs to be defined, only declared. decltype
therefore yields the result type.
struct C1 : A<int> {};
struct C2 : B<int> {};
struct C3 : A<int>, B<char> {};
static_assert(is_specialization_base_of<A, C1>::value);
static_assert(!is_specialization_base_of<B, C1>::value);
static_assert(!is_specialization_base_of<A, C2>::value);
static_assert(is_specialization_base_of<B, C2>::value);
static_assert(is_specialization_base_of<A, C3>::value);
static_assert(is_specialization_base_of<B, C3>::value);
I'll leave the exercise of plugging this trait into your template function to you. I'd just recommend turning those if
's into if constexpr
for some added goodies.
Upvotes: 3