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