C++:重载没有选择所需的方法
C++: overloading does not choose expected method
我有以下代码:
#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;}
现在,如果T
是A
或是从A
派生的类,则模板化函数的返回类型无效,并且它将从重载解析中排除。CCD_ 13存在于CCD_。
- 两种访问I2C总线的方法有什么区别?
- 私有虚拟方法有什么用?
- CRTP:为什么获得嵌套类型和派生类的嵌套方法有区别
- 这两种方法有什么区别?
- 有选择地禁用第三方库的C++核心准则检查器
- 有选择地禁用库中的死代码消除
- C++,有选择地应用模板模式来发挥作用
- 使用 SFINAE 有选择地实例化模板的成员函数
- 不实现父类的虚拟方法有什么风险
- 当一种方法有三种返回可能性时该怎么办?
- 我通过迭代加法将二进制数转换为十进制并检查单个字符(请参阅代码)的方法有什么问题?
- 有选择地隐藏类成员的成员
- 调用'maps::size(char [20], char&)'没有匹配函数,但我认为我的方法有
- 在 c++ 中使用用户输入初始化数组大小的不同方法有哪些
- 以下向 c++ 向量添加元素的方法有什么区别
- 有选择地启用一个并行区域内的OpenMP进行循环
- 我的堆栈中的弹出方法有什么问题?
- 有选择地对向量 c++ 进行排序
- 有选择地编译框架C++的方法
- 是否有一种方法可以选择我想要分配的一个实例的成员,而无需手动分配它们