什么是参数依赖查找?许多人也称它为Koenig Lookup。

最好我想知道:

为什么这是一件好事? 为什么这是一件坏事? 它是如何工作的?


当前回答

在我看来,并不是所有的东西都是好的。人们,包括编译器供应商,一直在侮辱它,因为它有时会有不幸的行为。

ADL负责对c++ 11中的for-range循环进行重大修改。要理解为什么ADL有时会产生意想不到的影响,不仅要考虑定义实参的名称空间,还要考虑实参的模板实参的实参,这些实参的函数类型的形参类型/指针类型的指针类型的形参类型,等等。

一个使用boost的例子

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

如果用户使用了boost,这将导致模糊性。因为可以同时找到std::begin(通过ADL使用std::vector)和boost::begin(通过ADL使用boost::shared_ptr)。

其他回答

在我看来,并不是所有的东西都是好的。人们,包括编译器供应商,一直在侮辱它,因为它有时会有不幸的行为。

ADL负责对c++ 11中的for-range循环进行重大修改。要理解为什么ADL有时会产生意想不到的影响,不仅要考虑定义实参的名称空间,还要考虑实参的模板实参的实参,这些实参的函数类型的形参类型/指针类型的指针类型的形参类型,等等。

一个使用boost的例子

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

如果用户使用了boost,这将导致模糊性。因为可以同时找到std::begin(通过ADL使用std::vector)和boost::begin(通过ADL使用boost::shared_ptr)。

在Koenig Lookup中,如果调用函数时没有指定其命名空间,那么函数名也会在定义参数类型的命名空间中进行搜索。这就是为什么它也被称为依赖参数的名称查找,简而言之就是ADL。

正是由于Koenig Lookup,我们可以这样写:

std::cout << "Hello World!" << "\n";

否则,我们将不得不这样写:

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");

这真的是太多的输入和代码看起来真的很难看!

换句话说,在没有Koenig Lookup的情况下,即使是Hello World程序看起来也很复杂。

Koenig Lookup(参数依赖查找)描述了c++中的编译器如何查找非限定名称。

c++ 11标准§3.4.2/1规定:

当函数调用(5.2.2)中的后缀表达式是一个非限定id时,可能会搜索其他在通常的非限定查找(3.4.1)中没有考虑到的名称空间,并且在这些名称空间中,可能会找到其他不可见的名称空间作用域友函数声明(11.3)。对搜索的这些修改取决于参数的类型(对于模板模板参数,则取决于模板的名称空间) 参数)。

尼科莱·约苏提斯简单地说:

如果函数的名称空间中定义了一个或多个参数类型,则不必为函数限定名称空间。

一个简单的代码示例:

namespace MyNamespace
{
    class MyClass {};
    void doSomething(MyClass) {}
}

MyNamespace::MyClass obj; // global object


int main()
{
    doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}

在上面的例子中,既没有using-declaration,也没有using-directive,但是编译器仍然通过应用Koenig查找正确地将未限定的名称doSomething()识别为在命名空间MyNamespace中声明的函数。

它是如何工作的?

该算法告诉编译器不仅要查看局部作用域,还要查看包含参数类型的名称空间。因此,在上面的代码中,编译器发现对象obj,即doSomething()函数的参数,属于命名空间MyNamespace。因此,它查看该名称空间来定位doSomething()的声明。

Koenig查找的优点是什么?

正如上面的简单代码示例所演示的,Koenig查找为程序员提供了方便和易于使用的功能。如果没有Koenig查找,程序员就会重复指定完全限定名,或者使用大量using-declarations。

为什么要批评柯尼格查找?

过度依赖Koenig查找会导致语义问题,有时会让程序员措手不及。

考虑std::swap的例子,它是交换两个值的标准库算法。使用Koenig查找时,在使用此算法时必须谨慎,因为:

std::swap(obj1,obj2);

可能不会表现出相同的行为:

using std::swap;
swap(obj1, obj2);

在ADL中,调用哪个版本的交换函数取决于传递给它的参数的名称空间。

如果存在命名空间a,并且存在a::obj1、a::obj2和a::swap(),那么第二个示例将导致对a::swap()的调用,这可能不是用户想要的。

此外,如果由于某种原因同时定义了A::swap(A::MyClass&, A::MyClass&)和std::swap(A::MyClass&, A::MyClass&),那么第一个示例将调用std::swap(A::MyClass&, A::MyClass&),但第二个示例将不会编译,因为swap(obj1, obj2)将是模糊的。

花絮:

为什么叫“柯尼格查找”?

因为它是由美国电话电报公司和贝尔实验室前研究员兼程序员安德鲁·柯尼格设计的。

进一步阅读:

赫布·萨特在GotW上的名字查找 标准c++ 03/11 [basic.lookup.]argdep]: 3.4.2依赖参数的名称查找。


Koenig查找的定义在Josuttis的书中定义,* c++标准库:教程和参考*。

也许最好先说为什么,然后再说怎么做。

当引入名称空间时,我们的想法是将所有内容都定义在名称空间中,这样独立的库就不会相互干扰。然而,这给运营商带来了一个问题。请看下面的代码:

namespace N
{
  class X {};
  void f(X);
  X& operator++(X&);
}

int main()
{
  // define an object of type X
  N::X x;

  // apply f to it
  N::f(x);

  // apply operator++ to it
  ???
}

Of course you could have written N::operator++(x), but that would have defeated the whole point of operator overloading. Therefore a solution had to be found which allowed the compiler to find operator++(X&) despite the fact that it was not in scope. On the other hand, it still should not find another operator++ defined in another, unrelated namespace which might make the call ambiguous (in this simple example, you wouldn't get ambiguity, but in more complex examples, you might). The solution was Argument Dependent Lookup (ADL), called that way since the lookup depends on the argument (more exactly, on the argument's type). Since the scheme was invented by Andrew R. Koenig, it is also often called Koenig lookup.

The trick is that for function calls, in addition to normal name lookup (which finds names in scope at the point of use), there is done a second lookup in the scopes of the types of any arguments given to the function. So in the above example, if you write x++ in main, it looks for operator++ not only in global scope, but additionally in the scope where the type of x, N::X, was defined, i.e. in namespace N. And there it finds a matching operator++, and therefore x++ just works. Another operator++ defined in another namespace, say N2, will not be found, however. Since ADL is not restricted to namespaces, you also can use f(x) instead of N::f(x) in main().