13

如果我使用给定的类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). 我该怎么做才能获得预期的行为?

谢谢

4

8 回答 8

11

这就是重载解析的工作原理。当查找完成时,它会同时找到模板和函数。然后推导出模板类型并开始重载决议。在类型参数的情况下,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));

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

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

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

在这种情况下,在查找之后,编译器将执行类型推导,它会推导TMyClassDer,然后它会评估函数的返回类型(SFINAE 也可以应用于另一个模板或函数参数)。is_base_ofwill yield并且不会有嵌套类型falseenable_if函数声明格式不正确,编译器将删除它,将解析集保留为单个候选者,即非模板重载。

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

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本身是在通用代码中完成的,否则函数名称的简单更改将解决问题,而不会不必要地增加您或其他维护者可能难以维护的复杂性。

于 2013-09-24T13:11:04.473 回答
4

为什么您的代码不起作用:请参阅@David 的出色解释。为了让它工作,你可以通过添加一个隐藏的模板参数来使用 SFINAE(“Substition Failure is not an 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 中,您需要添加常规默认参数或修改返回类型。

于 2013-09-24T13:06:30.110 回答
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 ();
}

如果您没有 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 ();
}
于 2013-09-24T13:07:12.860 回答
1

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

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

func<MyClassDer> (const MyClassDer& t)

而不是

func<MyClass> (const MyClass& t)

然后编译器选择第一个。


解决该问题的一种可能性是:

func(static_cast<MyClass&>(myClassDer));
于 2013-09-24T13:07:56.770 回答
0
MyClass *myClassDer = new MyClassDer;
func(*myClassDer);
delete myClassDer;
于 2013-09-24T12:56:46.860 回答
0

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

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

更多多态性示例和详细信息here

于 2013-09-24T12:59:02.273 回答
0

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

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

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

所以如果你想传递一个MyClassDer对象,你仍然可以使用多态来做到这一点。

MyClass *myClassDer = new MyClassDer;
func(*myClassDer);
于 2013-09-24T13:07:00.533 回答
0

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

MyClassDer myClassDer;
func(static_cast<MyClass&>(myClassDer));
于 2013-09-24T13:13:37.657 回答