带有抽象类的覆盖模板功能

Override template function with abstract class

本文关键字:功能 覆盖 抽象类      更新时间:2023-10-16

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

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

如果我从我制作的抽象类继承。

,我想超载此模板。
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())

我也尝试过超载它,尽管与INT一起使用,但它与我的基类无法使用。

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

如果您可以访问C 17,则可以创建一个将转发到正确函数的公共过载:

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

c 20解决方案

您可以在此处运行代码。

使用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 typen";
}
//This is the template that acts on classes derived from MyBase
void do_thing(derived_from<MyBase> const& x) {
    std::cout << "Doing thing on MyBasen";
}

由于第二个功能将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解决方案

您可以在此处运行代码。

我们可以使用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 typen";
}
// 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 MyBasen";
}

尽管声明很奇怪,但我们仍然可以使用do_thing,就好像它是常规功能:

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

将事物送给C 11

您可以在此处运行代码。

我们只需要对C 11的Backport事物进行一些较小的更改。基本上,

  • is_base_of_v<MyBase, T>必须由is_base_of<MyBase, T>::value代替,
  • enable_if_t</* condition */>必须由typename enable_if</* condition */>::type
  • 代替
B b;
func((A&)b);

我认为您不能在这里通过临时性。您不能将b((铸造为" a&amp;"因为它是一个rvalue,并将其投入到consta&amp;使模板版本成为更好的匹配。

其他选项(在其他答案之上(使用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());
}