I was wondering if it is possible to have a template specialization accept a class and its subclasses. Like so:
class A {};
class B : public A {};
template <typename T>
void foo(const T& t) {
printf("T");
}
template <>
void foo(const A& t) {
printf("A");
}
int main(int argc, char** argv) {
B b;
foo(b);
return 0;
}
Currently it output 'T' because b
doesn't have its own template specialization, so it defaults to printing 'T'. I was wondering if it was possible for B
to use the template specialization of A
since B
is a subclass of A
. Or is that just not a thing?
Note: Because of some requirement, I can't use copy/move.
Note: I would also prefer if I didn't need to change A
or B
, but lets see what is possible first.
There may be a cleaner way to do this. But if you change the actual implementation of foo to a SFINAE functionoid object like std::hash, you can keep your default overload without polluting it with all the potential overload conditions. (Credit Arthur O'Dwyer's blog).
How about this:
It is definitely possible and there is no need for
-copy/move,
-change in the classes,
-change in the body of the functions (including but not limited to calling another class template static function),
-change in the return type/s of the function/s,
-or even using a constant expression as the return type/s of the function/s!
Solution
It is just needed to have the general function as a function template which employes SFINAE technique in form of default template type/s argument for extra template type parameter/s in order to avoid the special cases and to have the specialization/s as normal function/s:
Explanation:
For general cases the default template type argument
std::enable_if_t<!std::is_base_of_v<A, T> > >
can be deduced from the first template type argumentT
. Because It exists and it is well defined, function template will be called.When the function is called with an object of a type based on class
A
becausestd::enable_if_t<!std::is_base_of_v<A, T> > >
is not defined the default template type argument does not exists hence template type parameter can not be deduced. So compiler would look for another functions with the same name and similar parameter type so the normal functionvoid foo(const A& t) { printf("A");}
would be called and there will be no ambiguity.Notes on Usage
For a new specializations simply add one more similar pseudo class to the (one) function template and write a function(non-template) for the new specialization.
If the template default type argument looks big and confusing one can simply create a policy template and use that instead. Like:
Also since before C++17 certain features are not enabled, for lower C++ versions, one can write the template like:
I addition if one chooses to use solution of @songyuanyao which uses a constant expression as the return type of the functions, If the return type of the functions is not
void
, for examplereturn_type
, The solution becomes like:Further Example
Finally for a better understanding of SFINAE One can consider a not-generally-correct/not-all-encompassing alternative solution which does not require any library:
Although this solution also works for this specific example, please note that this solution is not always correct. Because it only tests conversion of T to A (which even in itself is not complete and problematic) not the inheritance. Specially for these kind of functions which are supposed to be called with objects of similar types, there is a great chance that many of these types would be convertible to each other!
I think the proper way for an inheritance test includes non-mutually conversion tests and determining if none of the types is
void*
. All considered It is much better to use std::is_base_of or std::is_base_of_v. However Thestruct ifnot
is OK and one can even exchangestd::enable_if
for it with the proper changes in their usage.Good luck!
The probelm is, the primary template is an exact match when
T
being deduced asB
; it's a better match than the specialization.You can use template overloading instead; with SFINAE.
LIVE