奇怪的功能查找
Strange function look up
当试图掌握std::ostream_iterator时,我想出了以下无法编译的代码(在gcc 5.3或clang 3.6下)。
#include <iostream>
#include <iterator>
namespace temp {
struct Point {
int x;
};
}
//namespace temp {
//namespace std {
std::ostream& operator<<(std::ostream& s, temp::Point p) {
return s << p.x;
}
//}
int main(int argc, char** argv) {
temp::Point p{1};
std::ostream_iterator{std::cout} = p;
//std::cout << p;
std::cout << std::endl;
return 0;
}
当operator<<
在全局范围内时,编译会引发大量模板实例化错误。
但是,std::cout << p
工作正常。而且,如果operator<<
在namespace temp
或namespace std
中声明,代码将按照预期编译和运行。
我的问题是为什么全球operator<<
不起作用?
您观察到的行为是两阶段查找过程的一个特点,在解析从模板定义引用的名称及其与参数相关查找 (ADL) 的交互时使用。
在您的情况下,您使用 operator =
来自 std::ostream_iterator
.从std::ostream_iterator::operator =
定义中引用的名称将通过两阶段查找进行查找:非依赖名称在第一阶段查找(从operator =
的定义中查找),而从属名称从实例化点查找(您对operator =
的调用)。
在内部,std::ostream_iterator::operator =
对给定(stream, value)
对使用运算符<<
。由于value
的类型取决于模板参数,因此对运算符<<
的引用被视为依赖引用。因此,其解决被推迟到第二阶段。
的确,查找的第二阶段(从实例化点执行)通常比第一阶段看到更多的名称。而且您显然希望全局命名空间中对operator <<
的定义也变得可见。
但是,请务必注意有关第二阶段的一个重要细节:在第二阶段,只有关联的命名空间(由 ADL 引入的命名空间)被"丰富",并在实例化点显示其他名称。但是"常规"命名空间(与 ADL 无关)完全不受第二阶段的影响。在后面的命名空间中,编译器仍然只能看到在第一阶段可见的相同名称,而不能看到其他任何名称。
这正是标准中以下段落所说的
14.6.4 从属名称解析 [temp.dep.res]
1 在解析从属名称时,将考虑来自以下来源的名称:
— 在定义 模板。
— 来自与 类型关联的命名空间的声明 来自实例化上下文的函数参数 (14.6.4.1) 以及从定义上下文。
这解释了在您的情况下会发生什么。即使向全局命名空间添加了额外的operator <<
,在这种情况下,全局命名空间也不是 ADL 关联的命名空间之一(只有 std
和 temp
是)。因此,第二阶段无法真正看到您的额外<<
定义。
但是,如果将定义添加到 ADL 关联的命名空间之一,第二阶段将立即注意到该添加。这就是为什么如果在 std
或 temp
命名空间中定义运算符,代码可以很好地编译。
这条线有两个问题(除了它没有意义之外):
std::ostream_iterator{std::cout} = p;
首先,std::ostream_iterator
是一个类模板,而不是一个类。所以你可能的意思是:
std::ostream_iterator<Point>{std::cout} = p;
现在,ostream_iterator::operator=
实际上是如何工作的?它确实依赖于operator<<
,但是在类模板的成员函数定义的上下文中。因此,它将找到的重载是那些在ostream_iterator
的operator=
范围内(你的不是)和那些可以在参数的相关命名空间中找到的重载(你的不是)。这就是查找失败的原因。
如果您只是将operator<<
移动到namespace temp
:
namespace temp {
std::ostream& operator<<(std::ostream& s, Point p) {
return s << p.x;
}
}
或作为非会员朋友:
namespace temp {
struct Point {
int x;
friend std::ostream& operator<<(std::ostream& s, Point p) { ... }
};
}
然后依赖于参数的查找成功,这有效:
std::ostream_iterator<Point>{std::cout} = p;
也就是说,不要编写该代码。使用普通std::cout << p
。
这是同一现象的另一个例子,可能更容易理解。 假设我们有一些函数模板,它只是在其参数上调用另一个函数:
template <class T>
void call_f(T val) {
f(val);
}
f
将通过从call_f
定义点查找或通过对val
的参数相关查找来找到。因此,如果我们稍后执行以下操作:
namespace N {
struct X { };
}
void f(N::X ) { }
int main() {
f(N::X{}); // ok
call_f(N::X{}); // error: can't find 'f'
}
该行错误是因为从call_f
的定义来看,没有函数f()
(根本没有),也没有函数f
namespace N
。但是,如果我们f
移动到该命名空间中,则两个版本都可以正常工作:
template <class T>
void call_f(T val) { f(val); }
namespace N {
struct X { };
void f(X ) { }
}
int main() {
f(N::X{}); // ok
call_f(N::X{}); // now ok too, ADL finds N::f
}
你想用这条线做什么:
std::ostream_iterator{std::cout} = p;
至于您的实际问题,您可以在全局范围内定义operator<<()
:
#include <iostream>
#include <iterator>
namespace temp {
struct Point {
int x;
};
}
std::ostream& operator<<(std::ostream& s, temp::Point p) {
return s << p.x;
}
int main(int argc, char** argv) {
temp::Point p{1};
//std::ostream_iterator{std::cout} = p;
std::cout << p;
std::cout << std::endl;
}
编译并输出1
.
- 有没有一种方法可以创建一个带有哈希表的数据库,该哈希表具有恒定时间查找功能
- C++没有标准功能的立方体根查找器
- STL查找功能从Deque返回了什么
- 使用C RTTI(内置)通过字符串查找功能指针
- Trie 查找/添加功能无法正常工作
- 模板功能中的名称查找规则
- 功能查找和名称空间
- C 功能:读取直到文件结束 - 查找代码中的错误
- 如何查找从哪里导入程序C++特定功能
- Qt使用QWebEngine查找单词功能
- 我如何避免使用依赖于参数的查找明确专门化模板化功能
- C 与查找功能一起使用时,迭代器为什么行为会有所不同
- 朋友功能模板查找
- 如何分析、查找和修复C++功能中的安全漏洞?
- 链表查找功能C++
- 查找功能不起作用
- 使用设置查找功能,未找到运算符
- 地图的查找功能得到错误的结果
- 不确定查找功能在 CPP 中的工作原理
- 我是否正确使用了 std::map 的查找功能?想要访问类数据