为什么编译器在这种情况下选择了不正确的函数重载
Why does the compiler choose the incorrect function overload in this case?
我正在尝试Sean Parent在GoingNative 2013演讲中提出的代码——"继承是邪恶的基类"。(上一张幻灯片中的代码,位于https://gist.github.com/berkus/7041546
我试着自己实现同样的目标,但我不明白为什么下面的代码不会像我期望的那样发挥作用。
#include <boost/smart_ptr.hpp>
#include <iostream>
#include <ostream>
template <typename T>
void draw(const T& t, std::ostream& out)
{
std::cout << "Template version" << 'n';
out << t << 'n';
}
class object_t
{
public:
template <typename T>
explicit object_t (T rhs) : self(new model<T>(rhs)) {};
friend void draw(const object_t& obj, std::ostream& out)
{
obj.self->draw(out);
}
private:
struct concept_t
{
virtual ~concept_t() {};
virtual void draw(std::ostream&) const = 0;
};
template <typename T>
struct model : concept_t
{
model(T rhs) : data(rhs) {};
void draw(std::ostream& out) const
{
::draw(data, out);
}
T data;
};
boost::scoped_ptr<concept_t> self;
};
class MyClass {};
void draw(const MyClass&, std::ostream& out)
{
std::cout << "MyClass version" << 'n';
out << "MyClass" << 'n';
}
int main()
{
object_t first(1);
draw(first, std::cout);
const object_t second((MyClass()));
draw(second, std::cout);
return 0;
}
这个版本可以很好地处理打印int
,但在第二种情况下无法编译,因为编译器不知道如何将MyClass
与operator<<
一起使用。我不明白编译器为什么不选择专门为MyClass
提供的第二个重载。如果我更改model::draw()方法的名称并从其主体中删除::
全局命名空间说明符,或者如果我将MyClass的draw全局函数更改为完整的模板专用化,则代码会编译并运行良好。
我得到的错误消息如下,之后是一堆candidate function not viable...
t76_stack_friend_fcn_visibility.cpp:9:9: error: invalid operands to binary expression ('std::ostream' (aka 'basic_ostream<char>') and 'const MyClass')
out << t << 'n';
~~~ ^ ~
t76_stack_friend_fcn_visibility.cpp:36:15: note: in instantiation of function template specialization 'draw<MyClass>' requested here
::draw(data, out);
^
t76_stack_friend_fcn_visibility.cpp:33:9: note: in instantiation of member function 'object_t::model<MyClass>::draw' requested here
model(T rhs) : data(rhs) {};
^
t76_stack_friend_fcn_visibility.cpp:16:42: note: in instantiation of member function 'object_t::model<MyClass>::model' requested here
explicit object_t (T rhs) : self(new model<T>(rhs)) {};
^
t76_stack_friend_fcn_visibility.cpp:58:20: note: in instantiation of function template specialization 'object_t::object_t<MyClass>' requested here
const object_t second((MyClass()));
^
为什么选择全局绘制模板函数的模板版本而不是MyClass函数重载?是因为模板引用是贪婪的吗?如何解决此问题?
因为在函数调用中使用了限定名称。[temp.dep.candidate]:
对于依赖于模板参数的函数调用使用通常的查找规则(3.4.1,3.4.2、3.4.3),但除外
- 对于使用非限定名称查找(3.4.1)或限定名称查找的查找部分(3.4.3)找到模板定义上下文
- 对于使用关联名称空间的查找部分(3.4.2),只有在模板定义中找到的函数声明上下文或模板实例化上下文
§3.4.2(别名〔basic.lookup.argdep〕):
当函数调用(5.2.2)中的后缀表达式是不合格id时,可以搜索在通常的不合格查找(3.4.1>)过程中未考虑的其他命名空间,并且在这些命名空间中,命名空间作用域友元函数声明(11.3)可以找到可见的。
因此,本质上ADL不适用,因为调用使用了限定id
正如Barry在回答中所示,您可以通过使呼叫不合格来解决此问题:
void draw(std::ostream& out) const
{
using ::draw;
draw(data, out);
}
不过,您必须在此之前添加一个using
-声明。否则,在按升序搜索声明性区域时,非限定名称查找将首先找到model<>::draw
成员函数,并且不会再进行搜索。但不仅如此,因为model<>::draw
(它是一个类成员)被我的非限定名称查找发现,ADL被没有调用,[basic.lookup.argdep]/3:
设
X
为非限定查找(3.4.1.)产生的查找集设Y
为参数相关查找生成的查找集(定义如下)。如果X
包含
- 类成员的声明,或
- 块作用域函数声明不是using声明,或者
- 既不是函数也不是函数模板的声明
则CCD_ 13为空。否则,
Y
是在与参数类型,如下所述。
因此,如果提供了using
声明,则通过非限定名称查找找到的唯一声明将是引入model::draw
声明性区域的全局draw
模板。ADL然后被调用,并为CCD_ 19找到后来声明的CCD_。
当您直接调用::draw()
时,您无法正确使用ADL。(为什么?我实际上不知道具体情况,希望有人也能进来向我解释这一点[编辑:请参阅Columbo的答案和原因])但为了真正使用ADL,你需要像这样对draw
打一个无条件的电话:
void draw(std::ostream& out) const
{
using ::draw;
draw(data, out);
}
这将正确地找到过载draw(const MyClass&, std::ostream&)
。
- 将参数传递给泛型 lambda 时复制构造函数不正确
- 非静态成员函数的 decltype 格式不正确吗?
- 数组为此合并排序函数提供了正确的输出,但向量给出了不正确的输出.出了什么问题?
- 我的动态链接队列在同一输出流中调用时不正确地输出三个返回函数
- 编译器在构造函数中计算的成员偏移量不正确
- 为什么在template函数广播中把两个extensor表达式加在一起不正确
- 从模板化类虚拟函数中调用的模板函数不正确
- 为什么不调用预期的函数?我是否对类型特征的理解不正确?
- 将函数的地址转换为UINTPTR_T给我不正确的结果
- 模板实例化失败:编译器选择不正确的重载函数
- 为什么构造函数C++接受不正确的类型作为参数?
- 不正确的输出和变量未用Eclipse CDT初始化构造函数
- 不正确的成员构造函数定义
- 使用POW()产生不正确值的立方函数
- 我将类型库(.tlb)导入到Delphi中,但函数参数似乎不正确.我应该如何解决它
- 为什么使用 MSVC 编译这个不正确的 std::函数初始化?
- 调用求和或获取乘积的函数不正确
- 为什么我的构造函数涉及shared_ptr不正确
- C++ 具有自定义比较函数的优先级队列在 push() 上行为不正确
- 使用 TBB 在向量中运行函数会给出不正确的输出