Selecting an explicit specialization of a class based on a derived type
Asked Answered
E

6

9

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

Enterovirus answered 22/1, 2010 at 16:38 Comment(1)
Now that's a tough question, I came up with an answer for methods that do not require Boost and do not require the writer of Foo to cater for all specializations by herself... however I only dealt with methods, ie it would not work for typedefs.Hilel
K
7

---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>(); }
};
Kaylenekayley answered 22/1, 2010 at 17:7 Comment(5)
And if you can't use Boost, you could look at how is_base_of works and do something similar.Murchison
Exactly. Something which looks like sizeof((A*)(B*(0)))!=0, though there isn't a chance this would directly work ! :)Lyndes
My only concern is that if I come up with a C class, and I want its derivatives to invoke C special behavior with Foo then I have to come back and edit Foo again... intrusive solutions and templates don't mix in my mind.Hilel
I'm still undecided as to which answer to select. I agree with Matthieu, in fact I actually came up against his very criticism, but I don't like his syntax. I think for general coders it would be quite hard to decipher as its a bit off the beaten track.Enterovirus
I hope my new version will help you decide !Lyndes
H
5

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;
}
Hilel answered 22/1, 2010 at 17:58 Comment(3)
Compiled and run under VC++2008.Hilel
Thanks for your very in depth answer and I agree with your intrusive argument but I'm still not quite sure I like the syntax you present here. It seems a little too subtle and off the beaten track. I solved the issue myself by creating a base class to A and just duplicating the functionality - not something I'm especially proud of but works.Enterovirus
I freely admit it is especially targetted at customization of templates that would respect derivation which is not what templates are usually meant to. By using concepts (see Boost.Concept) you could (I think) base the customization on the presence of a member function (static or not), which would thus work for derivation... but I haven't really research it.Hilel
B
1

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.

Bowhead answered 22/1, 2010 at 16:59 Comment(2)
You can specialise individual member functions of a class template without specialising the whole class.Murchison
@Mike Seymour: Right you are (second sentence of §14.7.2/1, for anybody who cares) -- it's something I'd never run into before. It doesn't affect the basic problem though. Instantiation with no conversion is a better match than instantiation that would require an implicit conversion.Bowhead
U
1

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; }
};
Unstuck answered 22/1, 2010 at 17:17 Comment(1)
And it's intrusive (ie, only works if you actually edit Foo).Hilel
I
0

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.

Infective answered 22/1, 2010 at 16:59 Comment(0)
H
0

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!
Haughty answered 22/1, 2010 at 18:42 Comment(1)
foo<A>::foobar(someA) will trigger an error: because if T is A then you basically have twice the same method... Furthermore it's intrusive.Hilel

© 2022 - 2024 — McMap. All rights reserved.