重载函数和继承

use*_*040 13 c++ inheritance templates sfinae overload-resolution

我试图重载一些模板函数来执行特定的操作,如果我使用给定的类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);
}
Run Code Online (Sandbox Code Playgroud)

输出是:

func template
func overloaded
MyClass
func template
Run Code Online (Sandbox Code Playgroud)

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

谢谢

Dav*_*eas 11

这就是重载解析的工作原理.查找完成后,它会找到模板和函数.然后推导出模板类型并开始重载解析.在类型论证的情况下,MyClass两个候选人是:

void func<MyClass>(MyClass const&);
void func(MyClass const&);
Run Code Online (Sandbox Code Playgroud)

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

void func<MyClassDer>(MyClassDer const&);
void func(MyClass const&);
Run Code Online (Sandbox Code Playgroud)

在这种情况下,第一个是比第二个更好的候选者,因为第二个需要派生到基础的转换并且被拾取.

有不同的方法来直接调度以命中您的代码.最简单的只是强制参数的类型,MyClass从而回退到原始案例:

func(static_cast<MyClass&>(myClassDer));
Run Code Online (Sandbox Code Playgroud)

虽然简单,但这需要在任何地方完成,如果你只是在一个地方忘记,那么就会调用错误的东西.其余的解决方案很复杂,您可能想要考虑提供不同的函数名称是否更好.

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

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

在这种情况下,在查找之后,编译器将执行类型推导,并且它将推断TMyClassDer,然后它将评估函数的返回类型(SFINAE也可以应用于另一个模板或函数参数).在is_base_of将产生falseenable_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()); 
}
Run Code Online (Sandbox Code Playgroud)

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

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

  • 你在几秒钟内输入了这个巨大的答案吗?+1 (2认同)

Rei*_*ica 3

您可以使用 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 ();
}
Run Code Online (Sandbox Code Playgroud)

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

实例

编辑

这应该可以在没有 C++11 的情况下工作(使用 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 ();
}
Run Code Online (Sandbox Code Playgroud)