2

I have created a template function, defined below.

template<class T>
void func(T t){ /* do stuff */ }

I would like to overload this template in the event that T inherits from an abstract class I made.

class A {
public:
    virtual void doStuff() = 0;
};
class B : public A {
    virtual void doStuff(){ /* do stuff */ }
}

I have tried using template specialization (below), but it still was using the original definition.

template<>
void func(A& a){ /* do different stuff */ } // Not called by func(B())

I tried overloading it as well, and while this worked with ints, it isn't working with my base class.

func(int i){ /* do stuff with i */ } // Called by func(3)
func(A& a){ /* do different stuff */ } // Not called by func(B())

I'm guessing this has to do with C++ not wanting to implicitly cast my instance of B to an A and reference it, but I haven't been able to find anything explaining how I can fix this behavior. Since A has a pure virtual function, I can't just define func(A a). Any help would be appreciated.

Here is an example in which the behavior I'm experiencing can be reproduced.

#include <iostream>

template<class T>
void func(T t){
    std::cout << "Template function called!" << std::endl;
}

class A {
public:
    virtual void doStuff() = 0;
};
class B : public A{
public:
    virtual void doStuff(){};
};

template<>
void func(const A& a){
    std::cout << "Specialized template called!" << std::endl;
}

void func(const A& a){
    std::cout << "Overload called!" << std::endl;
}

int main(){
    B b{};
    func(b);

    return 0;
}
James McDowell
  • 2,668
  • 1
  • 14
  • 27

3 Answers3

3

If you have access to C++17, then you can create a public overload that will forward to the correct function:

namespace detail {
  template<class T>
  void func(T t) {
    std::cout << "Template function called!" << std::endl;
  }

  void func(A& a){
    std::cout << "Overload called!" << std::endl;
  }
}

template<class T>
void func(T& t) {
  if constexpr (std::is_base_of_v<A, T>) {
    detail::func(static_cast<A&>(t));
  } else {
    detail::func(t);
  }
}

int main() {
  B b;
  func(b);
}

Otherwise you can use tag dispatching or SFINAE:

Tag dispatching:

template<class T>
void func(T t, std::false_type) {
  std::cout << "Template function called!" << std::endl;
}

void func(A& a, std::true_type) {
  std::cout << "Other function called!" << std::endl;
}

template<class T>
void func(T& t) {
  func(t, std::is_base_of<A, T>{});
}

SFINAE:

template<class T,
         std::enable_if_t<std::is_base_of_v<A, T>>* = nullptr>
void func(T t) {
  std::cout << "Template function called!" << std::endl;
}

template<class T,
         std::enable_if_t<!std::is_base_of_v<A, T>>* = nullptr>
void func(T& t) {
  std::cout << "Other function called!" << std::endl;
}
David G
  • 94,763
  • 41
  • 167
  • 253
2

C++20 solution

You can run the code here.

Using Concepts from C++20, we can write an inherits_from concept, and use that. Concepts allow us to constrain a template so that it only applies in situations where an expression is true.

The concept looks like this:

#include <type_traits>

template<class Derived, class Base>
concept derived_from = std::is_base_of_v<Base, Derived>;

Then, we can write the generic template and the constrained template:

struct MyBase{};
struct MyDerived : MyBase{}; 

// This is the generic template; using auto here is valid in C++20
void do_thing(auto const& thing) {
    std::cout << "Doing thing on regular type\n";
}

//This is the template that acts on classes derived from MyBase
void do_thing(derived_from<MyBase> const& x) {
    std::cout << "Doing thing on MyBase\n";
}

Because the second function declares T as following the concept inherits_from, it's more specialized, so for types that actually inherit from MyBase, it'll be selected over the generic template:

int main() {
    do_thing(10);           // Prints "Doing thing on regular type"
    do_thing(MyBase());     // Prints "Doing thing on MyBase"
    do_thing(MyDerived());  // Prints "Doing thing on MyBase"
}

C++17 solution

You can run the code here.

We can emulate the behavior of concepts using SFINAE, although this requires modifying the generic template so that it'll by ignored if T extends MyBase. The key to using SFINAE is to trigger a substitution failure if a condition is false, resulting in that overload being ignored.

In order to trigger a substitution failure, add a defaulted template argument at the end of the list of template parameters. In our case, it looks like this:

template<
    class T,
    // This defaulted argument triggers the substitution failure
    class = std::enable_if_v</* condition */>> 

In our code, - The generic overload will be disabled if T extends MyBase - The constrained overload will be disable if T doesn't extend MyBase

It looks like this:

struct MyBase {};
struct MyDerived : MyBase {};

template<
    class T,
    class = std::enable_if_t<!std::is_base_of_v<MyBase, T>>>
void do_thing(T const&) {
    std::cout << "Doing thing on regular type\n";
}

// This overload is *disabled* if T doesn't inherit from MyBase
template<
    class T,
    // We have to have an additional defaulted template argument to distinguish between the overloads
    class = void, 
    class = std::enable_if_t<std::is_base_of_v<MyBase, T>>>
void do_thing(T const& x) {
    std::cout << "Doing thing on MyBase\n";
}

Despite the weird declaration, we can still use do_thing as though it were a regular function:

int main() {
    do_thing(10);
    do_thing(MyBase());
    do_thing(MyDerived()); 
}

Backporting things to C++11

You can run the code here.

We only have to make a few minor changes to backport things to C++11. Basically,

  • is_base_of_v<MyBase, T> has to be replaced by is_base_of<MyBase, T>::value, and
  • enable_if_t</* condition */> has to be replaced by typename enable_if</* condition */>::type
Alecto Irene Perez
  • 10,321
  • 23
  • 46
0
B b;
func((A&)b);

I don't think you can pass a temporary here. You cannot cast B() to 'A&' because it is an rvalue and casting it to const A& makes the template version a better match.

Another options (on top of other answers) using c++11 - explicitly removing the template version if T is a subtype of B:

template<class T>
typename std::enable_if<!std::is_base_of<A, T>::value>::type
func(T& t){
    std::cout << "Template function called!" << std::endl;
}

void func(const A& a){
    std::cout << "Overload called!" << std::endl;
}

int main(){
  func(B());
}
log0
  • 10,489
  • 4
  • 28
  • 62
  • Does hard casting it like that cause any issues? For example, if B had other virtual functions inherited from other classes, would the v table still properly match up with that of A? – James McDowell Aug 05 '19 at 21:48
  • You can test that but for me it is exactly equivalent to `B b; A& a = b; func(a);` So no vtable issues. – log0 Aug 05 '19 at 21:51