Partial specialization of class templates is based on pattern matching. In contrast, customizing function templates is based on template argument deduction and overload resolution.
Because of the class hierarchy present in your problem, customizing behavior would in principle be more convenient through function template overloading, because that can take derived-to-base conversions into account. The pattern matching used in partial class template specialization does not provide the same flexibility.
However, since C++11, it is possible to do compile-time return type deduction. Here is a solution that combines tag dispatching, default constructors and decltype
type deduction:
#include <iostream>
// file foo_base.h:
struct foo_base
{
foo_base() = default;
};
foo_base faux(foo_base const&)
{
return foo_base{};
}
template<class T, class = decltype(faux(T{}))>
struct aux;
template<class T>
struct aux<T, foo_base>
{
enum { value = 1 };
};
// file foo.h:
template<typename T>
struct foo : foo_base
{
foo() = default;
};
// file bar.h:
template<typename T>
struct bar : foo<T>
{
bar() = default;
};
template<class T>
bar<T> faux(bar<T> const&)
{
return bar<T>{};
}
template<class T, class U>
struct aux<T, bar<U>>
{
enum { value = 2 };
};
// file meow.h
template<class T>
struct meow : bar<T>
{
meow() = default;
};
int main()
{
std::cout << aux<foo_base>::value; // 1
std::cout << aux<foo<int>>::value; // 1
std::cout << aux<bar<int>>::value; // 2
std::cout << aux<meow<int>>::value; // 2
}
Live Example that works with both g++ and clang in C++11 mode (C++14 mode is not required!).
The constexpr
function faux()
is overloaded for foo_bar
and as a function template for bar<T>
. Any argument whose class is derived from foo_base
but not from bar<T>
will select the former overload, and anything derived from bar<T>
will select the latter overload. This mechanism is the same as e.g. in the Standard Library where iterator categories are used to tag dispatch several implementations of std::advance()
e.g.
To use this selection mechanism during partial specialization of your class template aux
, two more ingredients are required. First, all classes are required to have a default constructor. Secondly, decltype()
is applied to that expression faux(T{})
to deduce the return type.
NOTE: it is not required that faux()
is constexpr
or that any of the default constructors are constexpr
, because decltype()
will
not actually evaluate the function call but only deduce its return
type.
The main class template aux
has a default template argument:
template<class T, class = decltype(faux(T{}))>
struct aux;
Partially specializing the second argument on foo_base
allows you to provide behavior for any class that derives from foo_base
:
template<class T>
struct aux<T, foo_base>
{ // custom behavior for anything derived from foo_bar };
The second partial specialization matches any class derived from any template instantiation bar<U>
template<class T, class U>
struct aux<T, bar<U>>
{ // custom behavior for anything derived from bar<U> for some U }
NOTE: the main drawback is that you might need to provide a default constructor to all classes in your hierarchy. This may or may not be an obstacle that you can overcome. Most classes already have a default constructor, but some may not. In that sense this solution is intrusive (i.e. it cannot be bolted on top of existing code, but it requires modification of that code).