用抽象覆盖模板函数 class

Override template function with abstract class

我创建了一个模板函数,定义如下。

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

如果 T 继承自我创建的抽象 class,我想重载此模板。

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

我已经尝试使用模板专业化(如下),但它仍然使用原始定义。

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

我也尝试过重载它,虽然它适用于整数,但不适用于我的基数 class。

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

我猜这与 C++ 不想将我的 B 实例隐式转换为 A 并引用它有关,但我无法找到任何解释我如何修复此行为的内容。由于 A 有一个纯虚函数,我不能只定义 func(A a)。任何帮助将不胜感激。

这是一个可以重现我遇到的行为的示例。

#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;
}
B b;
func((A&)b);

我认为你不能在这里临时通过。您不能将 B() 转换为 'A&',因为它是一个右值并将其转换为 const A& 使模板版本更匹配。

使用 c++11 的另一个选项(在其他答案之上)- 如果 T 是 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());
}

C++20 解决方案

You can run the code here.

使用 C++20 中的概念,我们可以编写一个 inherits_from 概念,然后使用它。概念允许我们约束模板,使其仅适用于表达式为真的情况。

这个概念是这样的:

#include <type_traits>

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

然后,我们就可以编写泛型模板和约束模板了:

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";
}

因为第二个函数将 T 声明为遵循概念 inherits_from,它更专业,所以对于实际继承自 MyBase 的类型,将选择泛型模板:

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 解决方案

You can run the code here.

我们可以使用 SFINAE 模拟概念的行为,尽管这需要修改通用模板,以便在 T 扩展 MyBase 时忽略它。使用 SFINAE 的关键是在条件为假时触发替换失败,从而导致该重载被忽略。

为了触发替换失败,在模板参数列表的末尾添加默认模板参数。在我们的例子中,它看起来像这样:

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

在我们的代码中, - 如果 T 扩展 MyBase,通用重载将被禁用 - 如果 T 扩展 MyBase

,约束重载将被禁用

看起来像这样:

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";
}

尽管声明很奇怪,我们仍然可以像使用常规函数一样使用 do_thing

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

向后移植 C++11

You can run the code here.

我们只需要做一些小的改动就可以向后移植到 C++11。基本上,

  • is_base_of_v<MyBase, T>要换成is_base_of<MyBase, T>::value,而
  • enable_if_t</* condition */> 必须替换为 typename enable_if</* condition */>::type

如果您可以访问 C++17,那么您可以创建一个 public 重载,它将转发到正确的函数:

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);
}

否则,您可以使用标签调度或 SFINAE:

标签调度:

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;
}