Template specialization does not take into account inheritance hierarchy. For example, if I specialize a template for Base
and instantiate it with Derived
, the specialization will not be chosen (see code (1) below).
This can be a major hindrance, because it sometimes lead to violation of the Liskov substitution principle. For instance, while working on this question, I noticed that I could not use Boost.Range algorithms with std::sub_match
while I could with std::pair
. Since sub_match
inherits publicly from pair
, common sense would dictate that I could substitute a sub_match
everywhere a pair
is used, but this fails due to trait classes using template specialization.
We can overcome this issue by using partial template specialization along with enable_if
and is_base_of
(see code (2)). Should I always favor this solution over full specialization, especially when writing library code? Are there any drawbacks to this approach that I have overseen? Is it a practice that you use or have seen used often?
Sample codes
(1)
#include <iostream>
struct Base {};
struct Derived : public Base {};
template < typename T >
struct Foo
{
static void f() { std::cout << "Default" << std::endl; }
};
template <>
struct Foo< Base >
{
static void f() { std::cout << "Base" << std::endl; }
};
int main()
{
Foo<Derived>::f(); // prints "Default"
}
(2)
#include <type_traits>
#include <iostream>
struct Base {};
struct Derived : public Base {};
template <typename T, typename Enable = void>
struct Foo
{
static void f() { std::cout << "Default" << std::endl; }
};
template <typename T>
struct Foo<
T, typename
std::enable_if< std::is_base_of< Base, T >::value >::type
>
{
static void f() { std::cout << "Base" << std::endl; }
};
int main()
{
Foo<Derived>::f(); // prints "Base"
}
enable_if
is more flexibleI think you should really prefer the enable_if approach: it enables everything that you could require and more.
E.g. there might be cases where a Derived class is Liskov-Subsitutable for a Base, but you [cannot assume/donot want to apply] the same traits/specializations to be valid (e.g. because the Base is POD class, whereas Derived ands non-POD behaviour or somehting like that that is completely orthogonal to class composition).
enable_if
gives you the power to define exactly the conditions.Hybrid approach
You could also achieve some middleground by implementing a traits class that derives some application-specific traits from general-purpose traits. The 'custom' traits could use the enable_if and meta-programming techniques to apply traits as polymorphically as you desire. That way, your actual implementations do not have to repeat some complicated enable_if/dispatch dance but instead can simply consume the custom-traits class (that hides the complexity).
I think some (many?) Boost libraries use the hybrid approach (I've seen it in some capacity where it bridges e.g. fusion/mpl, I think also various iterator traits in Spirit).
I personally like this approach because it can effectively isolate the 'plumbing' from the core-business of a library, making maintenance and documentation (!) a lot easier.