开发者

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


---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>(); }
};


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;
}


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.


Here's a solution, but it's not especially nice though:

template<typename T>
class Foo
{
public:
  int FooBar(typename disable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 10; }
  int FooBar(typename enable_if<boost::is_base_of<A,T> >::type* dummy = 0) { return 20; }
};


You need to specialize on exact type. For example Foo<A> fooA; fooA.FooBar(); would get you the 20. Or use boost.type_traits as @Benoît shows.


Fundamentally, you want to have a template specialization trigger on derived classes.

If you don't need the actual class to be specialized, just the function(s), it seems like you could just do:

int foo::foobar(A &someA);

If you then need to actually have the class be specialized, I think you want to look at interfaces and the private class data pattern; more or less, the interface "reduces" an object to a template-specialization recognized type, and then calls through; ala

int foo::foobar(A &someA)
{ return fooImpl<A>::foobar(someA); }

But I suppose that doesn't really answer your question, because it doesn't support the generic case. I suppose you could have:

template<class T>
class foo
{
public: 
    int foobar(T &t);
    int foobar(A &a);
}

foo<A>::foobar(someA);
foo<F>::foobar(someA);
foo<not B>::foobar(someB); //Should trigger foobar(A &a), right?

This would then be able to recognize B as derived from A, while still providing the generic case. I think; I haven't tested this.

It's not the prettiest, but I think you have some opportunity for some fun access control-like stuff in there, if you're specializing the actual class, as you can include or not include various foobar(A &a) like functions to allow or deny use on various inheritance trees; for the above example;

foo<C>::foobar(someF); //doesn't exist!
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜