Search code examples
c++templatesexplicit-specialization

Selecting an explicit specialization of a class based on a derived type


Hi I'm having problems selecting the correct version of a templated class which has an explicit specialization. I'm wanting to select a specialization using a derived class of the class used to specialize. The scenario is:

#include <stdio.h>

class A
{};

class B: public A
{};

template<typename T>
class Foo
{
public:
   int FooBar(void) { return 10; }
};

// Explicit specialization for A
template<> int Foo< A >::FooBar( void ) { return 20; }

void main( void)
{
   Foo<B> fooB;

   // This prints out 10 instead of wanted 20 ie compiler selects the general version
   printf("%d", fooB.FooBar() );
}

As I say in my comments there I want to see 20 being printed out because B is derived from A but 10 gets printed out instead. How do I go about getting the specialization called without resorting to writing a specialization for each and every derived class (my actual scenario has a lot of derived types).


Solution

  • ---EDIT : NEW ANSWER Let's make the original approach more maintainable. All the important choices can be found in the definition of Foo. It is supposed to be easy to maintain.

    #include <boost/mpl/if.hpp>
    #include  <boost/type_traits/is_base_of.hpp>
    #include <iostream>
    
    class A
    {};
    
    class B: public A
    {};
    
    class C{};
    class D : public C{};
    class E{};
    
    struct DefaultMethod
    {
        static int fooBar() { return 10; }
    };
    struct Method1
    {
        static int fooBar() { return 20; }
    };
    struct Method2
    {
        static int fooBar() { return 30; }
    };
    
    template<typename T, typename BaseClass, typename Choice1, typename OtherChoice>
    struct IfDerivesFrom :
        boost::mpl::if_<
            typename boost::is_base_of<BaseClass, T>::type,
            Choice1,
            OtherChoice>::type
    {
    };
    
    template<typename T>
    struct Foo :
        IfDerivesFrom<T, A,
          Method1,
          IfDerivesFrom<T, C,
              Method2,
              DefaultMethod>
          >
    {
    };
    
    int main()
    {
        std::cout << Foo<A>::fooBar() << std::endl;
        std::cout << Foo<B>::fooBar() << std::endl;
        std::cout << Foo<C>::fooBar() << std::endl;
        std::cout << Foo<D>::fooBar() << std::endl;
        std::cout << Foo<E>::fooBar() << std::endl;
    
        return 0;
    }
    

    ---ORIGINAL ANSWER If you can use boost, you can do something like the following :

    #include  <boost/type_traits/is_base_of.hpp>
    
    template<bool b>
    class FooHelper
    {
        int FooBar();
    };
    template<> FooHelper<true>::FooBar(){ return 20;}
    template<> FooHelper<false>::FooBar(){ return 10;}
    
    template<typename T>
    class Foo
    {
    public:
       int FooBar(void) { return FooHelper<boost::is_base_of<A, T>::type::value>(); }
    };