I am trying to overload some template function to perform specific action if I call it using a given class MyClass or any derived class MyClassDer. Here is the code:
#include <iostream>
struct MyClass {
virtual void debug () const {
std::cerr << "MyClass" << std::endl;
};
};
struct MyClassDer : public MyClass {
virtual void debug () const {
std::cerr << "MyClassDer" << std::endl;
};
};
template <typename T> void func (const T& t) {
std::cerr << "func template" << std::endl;
}
void func (const MyClass& myClass) {
std::cerr << "func overloaded" << std::endl;
myClass.debug ();
}
int main(int argc, char **argv) {
func (1);
MyClass myClass;
func (myClass);
MyClassDer myClassDer;
func (myClassDer);
}
The output is:
func template
func overloaded
MyClass
func template
func (myClassDer)
calls the template function instead of void func (const MyClass& myClass)
. What can I do to get the expected behavior?
Thanks
This is just how overload resolution works. When lookup completes it finds both the template and the function. The template types are then deduced and overload resolution starts. In the case of an argument of type MyClass
the two candiates are:
void func<MyClass>(MyClass const&);
void func(MyClass const&);
Which are equally good matches for the arguments, but the second being a non-template is preferred. In the case of MyClassDer
:
void func<MyClassDer>(MyClassDer const&);
void func(MyClass const&);
In this case the first is a better candidate than the second one, as the second one requires a derived-to-base conversion and that is picked up.
There are different approaches to direct dispatch to hit your code. The simplest is just coercing the type of the argument to be MyClass
and thus fallback to the original case:
func(static_cast<MyClass&>(myClassDer));
While simple, this needs to be done everywhere and if you forget in just one place, the wrong thing will be called. The rest of the solutions are complex and you might want to consider whether it would not be better to just provide different function names.
One of the options is using SFINAE to disable the template when the type is derived from MyClass
:
template <typename T>
typename std::enable_if<!std::is_base_of<MyClass,MyClassDer>::value>::type
func(T const & t) { ... }
In this case, after lookup, the compiler will perform type deduction, and it will deduce T
to be MyClassDer
, it will then evaluate the return type of the function (SFINAE could also be applied to another template or function argument). The is_base_of
will yield false
and the enable_if
won't have a nested type. The function declaration will be ill-formed and the compiler will drop it, leaving the resolution set with a single candidate, the non-template overload.
Another option would be providing a single template interface, and dispatching internally to either a template or the overload (by a different name) using tag-dispatch. The idea is similar, you evaluate the trait inside the template and call a function with a type generated from that evaluation.
template <typename T>
void func_impl(T const&, std::false_type) {...}
void func_impl(MyClass const&, std::true_type) {...}
template <typename T>
void func(T const &x) {
func_impl(x,std::is_base_of<MyClass,MyClassDer>::type());
}
There are other alternatives, but those are two common ones and the rest are mainly based on the same principles.
Again, consider whether the problem is worth the complexity of the solution. Unless the call to func
is itself done inside generic code, a simple change of the function name will solve the problem without unnecessarily adding complexity that you or the other maintainers might have problems maintaining.
For why your code didn't work: see @David's excellent explanation. To get it to work, you can use SFINAE ("Substition Failure is not an Errro) by adding a hidden template parameter Requires
(the name is for documentation purposes only)
template <
typename T, typename Requires = typename
std::enable_if<!std::is_base_of<MyClass, T>::value, void>::type
>
void func (const T& t) {
std::cerr << "func template" << std::endl;
}
This will disable this template for overload resolution whenever T
is equal to or derived from MyClass
, and will select the regular function instead (for which Derived-to-Base conversions will be performed, in contrast to template argument deduction, which considers exact matches only). You can obviously play around with this and add several overloads with non-overlapping conditions inside the std::enable_if
to have a fine-grained selection of function overloads that will be considered. But be careful, SFINAE is subtle!
Live Example.
Note: I wrote my SFINAE with C++11 syntax, using a default template parameter for function templates. In C++98 you need to add either a regular default parameter or modify the return type.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With