强制名称查找考虑名称空间范围
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
class 中定义基本发射运算符,但是任何定义新 classes 并希望提供发射方法的模块都可以通过提供一个有两个参数的友元函数。目前一切正常。
现在,让我们尝试在不重载运算符的情况下使用相同的想法,而是将计划成员函数用于基类型的 pre-defined emit 函数,并且仍然允许将 class-specific emit 函数定义为class:
的友元函数
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
换句话说,top-level emit
函数从不被考虑,而是在解析名称时只考虑 Output
class 中的成员函数。
我假设这是因为符号 emit
不是从属名称,因此在(模板的)定义点而不是实例化点查找,但第 14.6.2 节 §1在 C++ 标准中说(略有编辑):
[...] In an expression of the form:
postfix-expression (
expression-list opt )
where the postfix-expression is an identifier the identifier denotes a dependent name if and only if any of the expressions in expression-list is a type-dependent expression (14.6.2.2).
此外,在 14.6.2.2 ("Type-dependent expressions") §2 中说:
this
is type-dependent if the class type of the enclosing member function is dependent
其中,AIUI,就是这种情况。
所以,问题是:
- 为什么查找在此处的名称解析中不考虑
emit
的 top-level 版本?
- 是否可以使第二个示例以与第一个示例相同的方式工作,以便模板成员函数定义在实例化时同时考虑成员函数或命名空间作用域函数?
更新:将 post 的标题更改为更准确,并对标准中的引文进行了轻微编辑。
正如 Johannes 指出的那样,如果找到成员函数,则不会调用 ADL,并且需要 ADL 才能找到 Item
的友元声明。
所以,要回答第一个问题,C++标准(C++03版本)中的Section 3.4.2 §2说:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered. [...]
回答第二个问题,固定示例代码如下:
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:
For the purpose of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as through they were members of the derived class.
但这指的是从 B
派生的 class D
中的 using B::f
,因此不是完美匹配。
这个问题与
考虑这个例子,松散地基于 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
class 中定义基本发射运算符,但是任何定义新 classes 并希望提供发射方法的模块都可以通过提供一个有两个参数的友元函数。目前一切正常。
现在,让我们尝试在不重载运算符的情况下使用相同的想法,而是将计划成员函数用于基类型的 pre-defined emit 函数,并且仍然允许将 class-specific emit 函数定义为class:
的友元函数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
换句话说,top-level emit
函数从不被考虑,而是在解析名称时只考虑 Output
class 中的成员函数。
我假设这是因为符号 emit
不是从属名称,因此在(模板的)定义点而不是实例化点查找,但第 14.6.2 节 §1在 C++ 标准中说(略有编辑):
[...] In an expression of the form:
postfix-expression
(
expression-list opt)
where the postfix-expression is an identifier the identifier denotes a dependent name if and only if any of the expressions in expression-list is a type-dependent expression (14.6.2.2).
此外,在 14.6.2.2 ("Type-dependent expressions") §2 中说:
this
is type-dependent if the class type of the enclosing member function is dependent
其中,AIUI,就是这种情况。
所以,问题是:
- 为什么查找在此处的名称解析中不考虑
emit
的 top-level 版本? - 是否可以使第二个示例以与第一个示例相同的方式工作,以便模板成员函数定义在实例化时同时考虑成员函数或命名空间作用域函数?
更新:将 post 的标题更改为更准确,并对标准中的引文进行了轻微编辑。
正如 Johannes 指出的那样,如果找到成员函数,则不会调用 ADL,并且需要 ADL 才能找到 Item
的友元声明。
所以,要回答第一个问题,C++标准(C++03版本)中的Section 3.4.2 §2说:
If the ordinary unqualified lookup of the name finds the declaration of a class member function, the associated namespaces and classes are not considered. [...]
回答第二个问题,固定示例代码如下:
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:
For the purpose of overload resolution, the functions which are introduced by a using-declaration into a derived class will be treated as through they were members of the derived class.
但这指的是从 B
派生的 class D
中的 using B::f
,因此不是完美匹配。