问题
I'm doing template specialization for one of my classes, and I'm experiencing something unexpected..
This is my code:
class Base {};
class Derived : public Base {};
template<typename T>
void doSomething(T t) { cout << "All Types!" << endl; }
template<>
void doSomething(Base b) { cout << "Base!" << endl; }
int main() {
Derived d;
doSomething(d); //prints "All Types!"
return 0;
}
I have a template function doSomething(T) that accepts parameters of any type.. except for type Base class.
So I specialized the doSomething template for parameter of type Base,, so it does something different.
When I pass a Derived class to doSomething, however, it prints "All Types!", while I expected it to print "Base!", because Derived class is essentially a Base class too.
Why does this specialization not work for Derived?
Any way to make it work?
Thanks
>>> Test Link <<<
UPDATE:
Someone mentioned overriding instead of template specializing.. but how would I override function in this case?
If I instead had:
template<typename T>
void doSomething(T t) { cout << "All Types!" << endl; }
void doSomething(Base b) { cout << "Base!" << endl; }
then doSomething(d) would also print "All Types!" instead of "Base!",, because Derived2 object would simply be considered as Type template parameter
回答1:
When you doSomething(Derived)
you cause your template
to be speculatively instantiated with T=Derived
.
This works (no SFINAE), so it becomes a candidate.
doSomething(Base)
is either not considered, or is a worse match than doSomething(Derived)
.
Specialization simply changes the implementation of that one instantiation of doSomething
. It does not change how it is considered at all.
Overriding adds another override, which then competes with your template
version using the usual rules.
There are a few ways we can route calls to doSomething
that are passed a Base
or any class derived from base to a single implementation. I'll show 2.
First, tag dispatching.
namespace aux {
template<class T> void doSomething( std::true_type /* is Base */, T t ) {
// T is a class derived from Base
}
template<class T> void doSomething( std::false_type /* is Base */, T t ) {
// T is not class derived from Base
}
}
template<class T> void doSomething( T t ) {
aux::doSomething( std::is_base_of< Base, T >{}, std::forward<T>(t) );
}
(replace {}
with ()
, and drop std::forward<T>
in C++03)
Here, we explicitly route derived classes of Base
to a different override.
Another approach would be to SFINAE exclude the template
from consideration, and have an override:
template<class T>
typename std::enable_if< !std::is_base_of<Base, T>::value >::type
doSomething( T t ) { /* blah */ }
void doSomething( Base b ) { /* foo */ }
now the template
version is excluded from consideration by SFINAE, and the override is used instead.
I find tag dispatching to be more clean.
回答2:
You might solve it with std::enable_if:
#include <type_traits>
#include <iostream>
class Base {};
class Derived1 : public Base {};
class Derived2 : public Derived1 {};
template<typename T>
typename std::enable_if< ! std::is_base_of<Base, T>::value>::type
doSomething(const T&) { std::cout << "All Types!" << std::endl; }
template<typename T>
typename std::enable_if<std::is_base_of<Base, T>::value>::type
doSomething(const T&) { std::cout << "Base or Derived!" << std::endl; }
int main() {
// All Types!
doSomething(1);
// Base or Derived!
doSomething(Base());
doSomething(Derived1());
doSomething(Derived2());
return 0;
}
Note: The argument is const T&
, but having T as argument avoids the mentioned slicing, already.
Note: typename std::enable_if<std::is_base_of<Base, T>::value>::type
is the return type, which is an optional second template parameter of std::enable_if, defaulting to void.
来源:https://stackoverflow.com/questions/25064214/template-specialization-not-working-with-derived-class