带名称空间的编译器的有趣行为

Edu*_*yan 32 c++ namespaces compiler-errors argument-dependent-lookup

假设以下代码:

#include <iostream>
using namespace std;

namespace X
{
  class A{};

  void f(A a){}

  void g(int a){}
}

int main()
{
  X::A a;
  f(a);
  g(5);
}
Run Code Online (Sandbox Code Playgroud)

编译代码时,会发生以下编译错误:

main.cpp:在函数'int main()'中:
main.cpp:error:'g'未在此范围内声明

所以函数f编译得很完美,但事实g并非如此.怎么样?它们都属于同一名称空间.编译器是否从类型的参数中推断出该函数f属于X命名空间X::A?在这种情况下编译器如何表现?

tao*_*ocp 27

X::A a;
f(a);
Run Code Online (Sandbox Code Playgroud)

因为Argument-Dependent Lookup(也称为Koenig Lookup)而起作用.aA命名空间内的类的对象X,当编译器搜索可匹配的函数时f,它将X在这种情况下查看命名空间.有关更多信息,请参阅参数依赖查找.


Sha*_*our 19

这适用于函数调用表达式:

f(a);
Run Code Online (Sandbox Code Playgroud)

因为X::A属于的命名空间f由于参数依赖查找(ADL)而包含在函数的查找中,所以cppreference解释ADL如下:

依赖于参数的查找(也称为ADL或Koenig查找)是用于在函数调用表达式中查找非限定函数名称的规则集,包括对重载运算符的隐式函数调用.除了通常的非限定名称查找所考虑的范围和名称空间之外,还会在其参数的名称空间中查找这些函数名称.

依赖于参数的查找使得可以使用在不同命名空间中定义的运算符

这在C++标准部分3.4.2 参数Argument依赖名称查找中有所介绍:

函数调用(5.2.2)中的postfix-expression 是非限定id时,可以搜索在通常的非限定查找(3.4.1)期间未考虑的其他命名空间,并且在这些命名空间中,命名空间范围的朋友函数或函数可以找到不可见的模板声明(11.3)

接着说:

对于函数调用中的每个参数类型T,存在一组零个或多个关联的命名空间以及要考虑的一组零个或多个关联的类.命名空间和类的集合完全由函数参数的类型(以及任何模板模板参数的命名空间)决定.

并包括以下项目:

如果T是类类型(包括联合),则其关联的类是:类本身; 它所属的成员,如果有的话; 及其直接和间接基类.其关联的名称空间是其关联类是成员的名称空间.[...]

进一步向下提供了一个与您的问题类似的示例:

namespace NS {
  class T { };
  void f(T);
  void g(T, int);
}

NS::T parm;
void g(NS::T, float);

int main() {
  f(parm); // OK: calls NS::f
  extern void g(NS::T, float);
  g(parm, 1); // OK: calls g(NS::T, float)
}
Run Code Online (Sandbox Code Playgroud)

函数调用表达式:

g(5);
Run Code Online (Sandbox Code Playgroud)

不起作用,因为ADL不为基本类型的参数添加任何名称空间.

Herb Sutter在Gotw#30什么是同类课程中涵盖了ADL - 接口原理.


Nia*_*all 6

当代码时f(a),编译器void f(A a){}namespace X因为ADL(参数依赖查找,也称为Koenig查找)中找到函数.

A在命名空间中声明X,因此当编译器需要查找定义时f,它包含来自该命名空间的可能性,因为a类型的对象A在该命名空间中(如声明的那样X::A a;).

另一方面,int未在声明中声明namespace X,因此namespace X不包含在查找中.由于找不到相应的函数f,因此无法编译.