强制名称查找以考虑命名空间范围
Forcing name lookup to consider namespace scope
这个问题在一定程度上与实例化点和名称绑定有关,但并不完全相关。问题是关于标准及其如何解决在模板定义中查找符号的问题。
考虑这个例子,松散地基于ostream库:
// Output module
class Output {
public:
void operator<<(int);
void operator<<(double);
...
};
// Item module
class Item {
friend void operator<<(Output& obj, const Item& x) {
...
}
};
// Main program
int main() {
Output out;
Item item;
out << 3;
out << 2.0;
out << item;
}
在本例中,关键点是在使用输出模块的任何模块之前定义输出模块,并且有一个模块(Item
模块)使用输出模块来发出项。
这允许在Output
类内部定义基本的emit运算符,但任何定义新类并希望提供emit方法的模块都可以通过提供带有两个参数的友元函数来实现。到目前为止一切都很好。
现在,让我们尝试在不重载运算符的情况下使用相同的思想,而是对基本类型的预定义发射函数使用计划成员函数,并且仍然允许将特定于类的发射函数定义为类的友元函数:
class Output {
public:
template <class Type>
void emit(Type x) {
emit(*this, x);
}
void emit(int);
void emit(double);
};
class Item {
friend void emit(Output& obj, const Item& x) {
...
}
...
};
int main() {
Output out;
Item item;
out.emit(3);
out.emit(2.0);
out.emit(item);
}
与前面的代码相比,添加了一个模板函数,因为不需要根据类型有不同的调用约定。换句话说,应该可以使用约定out.emit(...)
,而不管发射的是什么项。
然而,当编译它(使用GCC 4.8.4)时,我们会得到以下错误:
example.cc: In instantiation of ‘void Output::emit(Type) [with Type = Item]’:
example.cc:49:20: required from here
example.cc:33:9: error: no matching function for call to ‘Output::emit(Output&, Item&)’
emit(*this, x);
^
example.cc:33:9: note: candidates are:
example.cc:32:12: note: template<class Type> void Output::emit(Type)
void emit(Type x) {
^
example.cc:32:12: note: template argument deduction/substitution failed:
example.cc:33:9: note: candidate expects 1 argument, 2 provided
emit(*this, x);
^
example.cc:36:12: note: void Output::emit(int)
void emit(int) {
^
example.cc:36:12: note: candidate expects 1 argument, 2 provided
example.cc:37:12: note: void Output::emit(double)
void emit(double) {
^
example.cc:37:12: note: candidate expects 1 argument, 2 provided
换句话说,在解析名称时,从不考虑顶级emit
函数,而只考虑Output
类内的成员函数。
我认为这是因为符号emit
不是一个从属名称,因此是在(模板的)定义点而不是实例化点查找的,但C++标准中的14.6.2§1节说(稍微编辑一下):
[…]在形式的表达式中:
nbsp nbsp后缀表达式
(
表达式列表opt)
其中后缀表达式是标识符标识表示依赖名称当且仅当表达式列表中的任何表达式是类型依赖表达式(14.6.2.2)
此外,在14.6.2.2("类型相关表达式")§2中,它说:
如果封闭成员函数的类类型是依赖的,则
this
是依赖类型的
AIUI就是这样。
因此,问题是:
- 为什么在这里的名称解析中查找不考虑
emit
的顶级版本 - 是否可以使第二个示例以与第一个示例相同的方式工作,以便模板成员函数定义在实例化时同时考虑成员函数或命名空间范围函数
更新:更改了帖子的标题,使其更加准确,并对标准中的引号进行了轻微编辑。
Johannes指出,如果发现成员函数,则不调用ADL,并且需要ADL来找到Item
的友元声明。
因此,为了回答第一个问题,C++标准(C++03版本)中的第3.4.2节§2说:
如果名称的普通非限定查找找到类成员函数的声明,则不考虑关联的命名空间和类。[…]
为了回答第二个问题,这里是固定的示例代码:
namespace emitter {
class Output;
template <class Type>
void emit(Output& out, const Type& t) {
emit(out, t);
}
class Output {
public:
template <class Type>
void emit(Type x) {
using emitter::emit;
emit(*this, x);
}
void emit(int);
void emit(double);
};
}
class Item {
friend void emit(emitter::Output& obj, const Item& x) {
...
}
};
然而,我仍在努力寻找澄清using
宣言解决问题的原因的段落。我现在能找到的最接近的是7.3.3§13:
为了解决重载问题,使用声明引入派生类的函数将被视为派生类的成员。
但这是指从B
派生的D
类中的using B::f
,因此不是完全匹配。
- 命名空间范围内的外部 - GCC vs clang vs msvc
- 具有命名空间范围的名称的 C++ 内部链接
- GCC 和非命名空间范围内的显式专用化
- 是否将命名空间范围文件本地 (.cpp) 常量放在匿名命名空间中
- Visual C:命名空间范围错误或晦涩功能?
- 命名空间范围问题
- 无法在命名空间范围内分配变量
- 非命名空间范围内的显式专用化不会在 GCC 中编译
- 如何处理警告 C4177:#pragma 'float_control' 只能在全局范围或命名空间范围内使用
- 为什么类型特征不适用于命名空间范围内的类型?
- 强制标准名称空间的命名空间范围
- 为什么我应该使用extern关键字来声明命名空间范围中的变量
- 关于友元函数定义和命名空间范围
- BOOST_STATIC_ASSERT的命名空间范围
- 为什么命名空间中的某些函数可以在没有命名空间范围前缀的情况下访问
- 在 cpp 文件中命名空间范围内"Private"变量
- 正在使用命名空间范围
- 强制名称查找以考虑命名空间范围
- 为什么语句不能出现在命名空间范围内
- 为什么不允许将类成员定义的' static '关键字放在命名空间范围内?