重载函数和继承

overloading function and inheritance

本文关键字:继承 函数 重载      更新时间:2023-10-16

如果我使用给定的类 MyClass 或任何派生类 MyClassDer 调用它,我正在尝试重载一些模板函数以执行特定操作。这是代码:

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

输出为:

func template
func overloaded
MyClass
func template

func (myClassDer)调用模板函数而不是void func (const MyClass& myClass) 。我该怎么做才能获得预期的行为?

谢谢

这就是重载解析的工作原理。查找完成后,它会同时查找模板和函数。然后推导模板类型并开始重载解析。在类型MyClass的参数的情况下,两个候选者是:

void func<MyClass>(MyClass const&);
void func(MyClass const&);

对于参数来说,这同样是很好的匹配,但第二个是非模板是首选。在MyClassDer的情况下:

void func<MyClassDer>(MyClassDer const&);
void func(MyClass const&);

在这种情况下,第一个比第二个更好,因为第二个需要派生到基数的转换,并且被拾取。

有不同的方法可以直接调度以命中代码。最简单的方法是强制MyClass参数的类型,从而回退到原始情况:

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

虽然简单,但这需要在任何地方完成,如果你只在一个地方忘记了,就会被称为错误的事情。其余的解决方案很复杂,您可能需要考虑仅提供不同的函数名称是否更好。

其中一个选项是在类型派生自 MyClass 时使用 SFINAE 禁用模板:

template <typename T>
typename std::enable_if<!std::is_base_of<MyClass,MyClassDer>::value>::type
func(T const & t) { ... }

在这种情况下,在查找之后,编译器将执行类型推断,并且它将推断出要MyClassDer T,然后它将评估函数的返回类型(SFINAE 也可以应用于另一个模板或函数参数(。is_base_of将产生false,并且enable_if将没有嵌套类型。函数声明的格式不正确,编译器将删除它,使分辨率集只有一个候选,即非模板重载。

另一种选择是提供单个模板接口,并使用 tag-dispatch 在内部调度到模板或重载(通过不同的名称(。这个想法是相似的,你评估模板中的特征,并调用一个函数,该函数从该评估生成一个类型。

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

还有其他选择,但这是两种常见的选择,其余的主要基于相同的原则。

同样,考虑问题是否值得解决方案的复杂性。除非对func的调用本身是在通用代码中完成的,否则函数名称的简单更改将解决问题,而不会不必要地增加您或其他维护者可能在维护时遇到问题的复杂性。

关于为什么你的代码不起作用:请参阅@David的精彩解释。要使其正常工作,您可以通过添加隐藏模板参数来使用 SFINAE("替代失败不是 Errro"(Requires(该名称仅用于文档目的(

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

这将在T等于或派生自MyClass时禁用此模板的重载解析,并将改为选择常规函数(将为其执行派生到基数转换,与模板参数推导相反,模板参数推导仅考虑完全匹配(。显然,您可以尝试一下,并在std::enable_if中添加几个具有非重叠条件的重载,以便对将要考虑的函数重载进行细粒度选择。但要小心,SFINAE 是微妙的!

活生生的例子

注意:我使用 C++11 语法编写了 SFINAE,使用函数模板的默认模板参数。在 C++98 中,您需要添加常规默认参数或修改返回类型。

您可以使用 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 ();
}

如果您没有 C++11,boost 将提供相同的功能。

现场示例

编辑

这应该在没有 C++11(使用提升(的情况下工作:

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

多态性发生在运行时,但选择重载函数发生在编译时。

因此,在编译时接受MyClassDer的最佳重载是

func<MyClassDer> (const MyClassDer& t)

而不是

func<MyClass> (const MyClass& t)

然后编译器选择第一个。


解决问题的可能性是:

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

您需要使用多态性才能调用模板函数。您需要对基类的引用:

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

更多多态性示例和详细信息,请参见此处

这是因为重载函数的签名是,

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

即它想要MyClass作为其参数,而您正在使用MyClassDer调用它。因此,在编译时,它会解析其他重载函数并与之链接。由于另一个函数是模板化的,因此编译器与之链接没有问题。

因此,如果要传递MyClassDer对象,仍然可以使用多态性来完成。

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

只需将其转换为基本类型:

MyClassDer myClassDer;
func(static_cast<MyClass&>(myClassDer));
MyClass *myClassDer = new MyClassDer;
func(*myClassDer);
delete myClassDer;