Вопрос

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

Это было полезно?

Решение 3

You can use SFINAE:

#include <type_traits>

template <typename T>
void func (const T& t, typename std::enable_if<!std::is_base_of<MyClass, T>::value>::type * = nullptr) {
    std::cout << "func template" << std::endl;
}

template <
    typename T
    , typename = typename std::enable_if<std::is_base_of<MyClass, T>::value>::type
>
void func (const T& t) {
    std::cout << "func overloaded" << std::endl;
    t.debug ();
}

If you don't have C++11, boost provides the same functionality.

Live example

EDIT

This should work without C++11 (using boost):

#include "boost/type_traits.hpp"

template <typename T>
void func (const T& t, typename boost::enable_if<!boost::is_base_of<MyClass, T>::value>::type * = 0) {
    std::cout << "func template" << std::endl;
}

template <typename T>
void func (const T& t, typename boost::enable_if<boost::is_base_of<MyClass, T>::value>::type * = 0) {
    std::cout << "func overloaded" << std::endl;
    t.debug ();
}

Другие советы

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.

Polymorphism occurs in run-time, but choosing an overloaded function occurs in compile-time.

So, in compile time the best overload to accept MyClassDer is

func<MyClassDer> (const MyClassDer& t)

rather than

func<MyClass> (const MyClass& t)

then compiler chooses the first.


A possibility to solve the issue is:

func(static_cast<MyClass&>(myClassDer));

You will need to use polymorphism in order to call your template function. You need a reference to your base class:

int main(int argc, char **argv) {
    func (1);
    MyClass myClass;
    func (myClass);
    MyClassDer myClassDer;
    MyClass* mc = &myClassDer;
    func (*mc);
}

More polymorphism examples and details here

Its because your overloaded function's signature is,

void func (const MyClass& myClass)
{
    std::cerr << "func overloaded" << std::endl;
    myClass.debug ();
}

i.e it wants MyClass as its parameter and you are calling it using MyClassDer. So at compile time it resolves the other overloaded function and links with that. As the other function is templated there is no problem for compiler to link with that.

So if you want to pass a MyClassDer object, you could still do it using polymorphism.

MyClass *myClassDer = new MyClassDer;
func(*myClassDer);

Just cast it to the base type:

MyClassDer myClassDer;
func(static_cast<MyClass&>(myClassDer));
MyClass *myClassDer = new MyClassDer;
func(*myClassDer);
delete myClassDer;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top