C++ C++;:重载不会选择预期的方法
我有以下代码:C++ C++;:重载不会选择预期的方法,c++,templates,inheritance,overloading,C++,Templates,Inheritance,Overloading,我有以下代码: #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) { c
#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);
}
代码编译后,输出为:
Specific case
Specific case
为什么模板化方法的存在会产生如此大的差异
编辑:如何强制编译器为存在时从中派生的类选择自由方法
模板化方法的调用?对模板实例化产生的
foo(const B&)
的调用不需要转换,因此它是更好的匹配
当编译器看到函数调用时,必须实例化每个基函数模板,并将其与每个普通函数一起包含在重载集中。然后执行重载解析。还有SFINAE,它允许函数模板的实例化导致错误(这样的函数不会添加到重载集)。当然,事情并不是那么简单,但它应该给出总体情况
关于编辑:只有一个方法可调用。还有什么可以作为输出?@pmr's解释了为什么在您的示例中首选模板函数。要强制编译器选择重载,可以使用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;}
模板
typename std::enable_iftype_traits
头。是的,这有点令人惊讶,但在重载解析方面,继承和模板并没有很好地结合
问题是,在评估应该选择哪个重载时,编译器选择需要最少转换的重载(内置到内置、派生到基、调用非显式构造函数或转换运算符等)。排名算法实际上相当复杂(并非所有转换都被视为相同的…)
一旦对重载进行了排序,如果最上面的两个排序相同,并且其中一个是模板,则该模板将被丢弃。但是,如果模板的级别高于非模板(通常转换次数较少),则会选择该模板
就你而言:
- 对于
而言,只有一个重载匹配,因此选择它std::vector
- 对于
两个重载匹配,它们的排名相等,模板一被丢弃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 case\n";
}
在行动中
这在C++03中也是可能的,但稍微麻烦一些。想法是一样的,省略号参数
…
的排名最差,因此我们可以对另一个函数使用重载选择来驱动对主函数的选择。是-只有一个方法可以简化事情。我想我也希望const B&call在这种情况下失败。B
是a
的直接子对象。它可以绑定到A&
或const A&
。现在我意识到我已经花了大约31分钟来编写我的答案。。。因此,我的时间被消耗殆尽:x@MatthieuM. 我为你感到难过。如果有索菲亚·安蒂波利斯的聚会,我会请你喝杯啤酒;)我不是也要从foo()那里退一些东西吗?如果我想保持方法签名不变呢。由于foo()的方法体,是否可以更改foo()内部的代码并使用enable_if来防止实例化?@ATempenable_if
有两个模板参数,其中第二个是返回类型。它默认为void
,这就是我在示例中省略它的原因。如果您有不同的返回类型,只需添加第二个模板参数。由于模板函数的主体中存在代码,因此无法阻止在重载解析期间选择模板函数。
template <typename T>
typename std::enable_if<not std::is_base_of<A, T>::value>::type
foo(T const& t) {
std::cerr << "Generic case\n";
}
// 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 case\n";
}
std::true_type has_specific_foo(A const&);