C++:重载没有选择所需的方法

C++: overloading does not choose expected method

本文关键字:方法 有选择 重载 C++      更新时间:2023-10-16

我有以下代码:

#include <iostream>
#include <vector>
using namespace std;
struct A{};
struct B: public A {};
template <typename T>
void foo(const T& obj) { cerr << "Generic case"<< endl;}
void foo(const A& a) {
    cerr << "Specific case" << endl;
}
int main() {
    vector<int> v;
    foo(v);
    B b;
    foo(b);
    A a;
    foo(a);
}

输出为

  • 一般情况
  • 一般情况
  • 具体案例

为什么没有为B对象选择foo(const A& a)

奇怪的是,如果我去掉模板化的方法,只得到以下内容:

#include <iostream>
#include <vector>
struct A{};
struct B: public A {};
//template <typename T>
//void foo(const T& obj) { cerr << "Generic case"<< endl;}
void foo(const A& a) {
    cerr << "Specific case" << endl;
}
int main() {
    B b;
    foo(b);
    A a;
    foo(a);
}

代码编译,输出为:

Specific case
Specific case

为什么模板化方法的存在会产生如此大的影响?

编辑:如何在存在的情况下强制编译器为从A派生的类选择免费方法模板化方法?

模板实例化产生的对foo(const B&)的调用不需要转换,因此它是更好的匹配。

当编译器看到函数调用时,必须实例化每个基函数模板,并将其与每个正常函数一起包含在重载集中。在执行过载解决之后。还有SFINAE,它允许函数模板的实例化导致错误(这样的函数不会添加到重载集)。当然,事情并没有那么简单,但它应该能给出总体情况。

关于您的编辑:只有一个方法可以调用。还有什么可以作为输出?

是的,这有点令人惊讶,但在重载解决方案方面,继承和模板并不能很好地结合在一起。

问题是,当评估应该选择哪个重载时,编译器会选择需要最少转换的重载(内置到内置、派生到基、调用非显式构造函数或转换运算符等)。排名算法实际上相当复杂(并非所有转换都被相同处理…)。

一旦对重载进行了排名,如果最前面的两个排名相同,并且其中一个是模板,则该模板将被丢弃。但是,如果模板的排名高于非模板(通常转换较少),则会选择该模板。

在您的情况下:

  • 对于std::vector<int>,只有一个重载匹配,因此它被选中
  • 对于A,两个重载匹配,它们的排名相等,丢弃模板一个
  • 对于B两个重载匹配,模板秩更高(不需要派生到基的转换),则选择它

有两种解决方法,最简单的是"修复"呼叫站点:

A const& ba = b;
foo(ba);

另一个是修复模板本身,但这更棘手。。。

您可以硬编码,对于从A派生的类,这不是您希望的重载:

template <typename T>
typename std::enable_if<not std::is_base_of<A, T>::value>::type
foo(T const& t) {
  std::cerr << "Generic casen";
}

然而,这并不是那么灵活。。。

另一个解决方案是定义一个钩子。首先,我们需要一些元编程实用程序:

// Utility
template <typename T, typename Result = void>
struct enable: std::enable_if< std::is_same<T, std::true_type>::value > {}; 
template <typename T, typename Result = void>
struct disable: std::enable_if< not std::is_same<T, std::true_type>::value > {}; 

然后我们定义我们的钩子和函数:

std::false_type has_specific_foo(...);
template <typename T>
auto foo(T const& t) -> typename disable<decltype(has_specific_foo(t))>::type {
  std::cerr << "Generic casen";
}

然后对于每个基类,我们想要一个特定的foo:

std::true_type has_specific_foo(A const&);

在ideone的行动中。

它在C++03中也是可能的,但稍微麻烦一些。不过,这个想法是一样的,省略号参数...的秩最差,所以我们可以在另一个函数上使用重载选择来驱动主函数的选择。

@pmr的回答解释了为什么在您的示例中首选模板化函数。要强制编译器选择重载,可以使用SFINAE从重载集中删除模板化函数。将模板化的foo更改为

template <typename T>
typename std::enable_if<!std::is_base_of<A, T>::value>::type
  foo(const T& obj) { cerr << "Generic case"<< endl;}

现在,如果TA或是从A派生的类,则模板化函数的返回类型无效,并且它将从重载解析中排除。CCD_ 13存在于CCD_。