13

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

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
user2811040
  • 133
  • 4

8 Answers8

11

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.

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • 2
    Did you type this huge answer just in a few seconds? +1 – masoud Sep 24 '13 at 13:17
  • @MM.: More like a couple minutes... I am not a slow typer, but I am not the Flash either – David Rodríguez - dribeas Sep 24 '13 at 13:40
  • @MM. The Holy Trinity of Name Lookup, Argument Deduction and Overload Resolution appears so often that it is worthwhile to have this more or less in working memory when writing template code. See e.g. Stephan T. Lavavej's lectures on [Core C++](http://channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-) – TemplateRex Sep 24 '13 at 14:33
  • 1
    @TemplateRex: That is if your brain L1 cache has enough capacity for the lookup rules... mine doesn't for sure. I maintain simple lookup, type deduction and simple resolution in L1, rest of resolution and second layer of lookup in RAM, the rest of lookup in HD (literally: the standard PDF) – David Rodríguez - dribeas Sep 24 '13 at 17:26
  • lol, that is a nice summary of your brain's memory hierarchy! In any case, I bet this particular answer came directly from L1? – TemplateRex Sep 24 '13 at 17:43
4

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.

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
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 ();
}
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
1

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));
masoud
  • 55,379
  • 16
  • 141
  • 208
0
MyClass *myClassDer = new MyClassDer;
func(*myClassDer);
delete myClassDer;
HAL
  • 3,888
  • 3
  • 19
  • 28
0

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

Chris L
  • 2,262
  • 1
  • 18
  • 33
0

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);
shofee
  • 2,079
  • 13
  • 30
0

Just cast it to the base type:

MyClassDer myClassDer;
func(static_cast<MyClass&>(myClassDer));
Eric Finn
  • 8,629
  • 3
  • 33
  • 42
  • static_cast is not a good solution for me since I can't really expect users of my function to use this construct. Futhermore my function will actually be an operator! – user2811040 Sep 24 '13 at 13:31
  • @user2811040 Ah. And I suppose you want the users of your function to be able to extend MyClass themselves, too. In that case, you seem to have accepted the right answer. – Eric Finn Sep 24 '13 at 13:51