Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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).

like image 403
user176168 Avatar asked Jan 22 '10 16:01

user176168


3 Answers

---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>(); }
};
like image 127
Benoît Avatar answered Sep 24 '22 21:09

Benoît


More generally, it's a long standing issue with template and inheritance in general.

The problem is that template work on exact types and do not consider inheritance factor, the two concepts being somewhat orthogonal, and thus try to mix one and the other is often error prone.

You could also check it out with methods:

template <class T>
int fooBar(T) { return 10; }

int fooBar(A) { return 20; }

B b;
fooBar(b); // this returns 10, because fooBar<T> is a better match (no conversion)

Now, on to your problems, while I appreciate the various solutions that have been given using enable_if and is_base_of tricks, I discard them as not being practical. The point of a specialization is that the author of Foo does not have to know about how anyone is going to specialize her class if necessary, merely to make it easy. Otherwise if you need a dozen specialization, you end up with a very very odd Foo class, that's for sure.

The STL has already dealt with similar problems. The accepted idiom is usually to provide a traits class. The default traits class provides a good solution for everyone while one can specialize a traits class to accommodate one's need.

I think there should be a way using Concepts (ie, if T defines T::fooBar() then uses it, otherwise uses the default version...), but for the specific of method overloading this is not required.

namespace detail { int fooBar(...) { return 10; } }

template <class T>
class Foo
{
public:
  static int FooBar() { T* t(0); return ::detail::fooBar(t); }
};

And now, to specialize for derived classes of A:

namespace detail { int fooBar(A*) { return 20; } }

How does it works ? When considering overloads, the ellipsis is the last method considered, so any that qualifies before will do, therefore it is perfect for a default behavior.

Some considerations:

  • namespace: depending on wether the identifier fooBar is likely to be used or not, you may prefer to isolate into a namespace of its own (or dedicated to the Foo class), otherwise, make an unqualified call and let the user define it in the namespace of her class.

  • this trick only works for inheritance and method invocation, it does not work if you wish to bring in special typedefs

  • you can pass more templates to the actual method, like the real type

Here is an example with template functions

namespace detail { template <class T> int fooBar(...) { return 10; } }

template <class T>
int Foo<T>::FooBar() { T* t(0); return ::detail::fooBar<T>(t); }

namespace detail {
  template <class T>
  int fooBar(A*)
  {
    return T::FooBar();
  }
}

And here what will happen:

struct None {};
struct A { static int FooBar() { return 20; } };
struct B: A {};
struct C: A { static int FooBar() { return 30; } };

int main(int argc, char* argv[])
{
  std::cout << Foo<None>::FooBar()  // prints 10
     << " " << Foo<A>::FooBar()     // prints 20
     << " " << Foo<B>::FooBar()     // prints 20
     << " " << Foo<C>::FooBar()     // prints 30
     << std::endl;
}
like image 35
Matthieu M. Avatar answered Sep 22 '22 21:09

Matthieu M.


First (minor) point: your title is incorrect; this is explicit specialization, not partial specialization. To get partial specialization, you need to specify at least one template parameter, but leave at least one other unspecified:

template <class T, class U>
demo { };

template <class T>
demo<int> {};  // use in the case of demo<XXX, int> 

Looking at your code, I'm a bit surprised that it compiles at all. I'm not sure there's any way you could force your specialized function to be called. Normally, you'd specialize the class as a whole:

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

template<>
class Foo<A> {
public:
    int FooBar() { return 20; }
};

In this case, that won't really do you any good though. You can convert a derived object to a base object implicitly, but it is still a conversion. On the other hand, the un-specialized version of the template can be used with no conversion -- and when picking which one to use, the compiler treats one that can be instantiated with no conversion as a better choice than one that requires an implicit conversion.

like image 35
Jerry Coffin Avatar answered Sep 24 '22 21:09

Jerry Coffin