什么是“参数相关查找” (又名 ADL,或“Koenig Lookup”)?

发布于 2024-12-15 19:17:10 字数 135 浏览 3 评论 0原文

关于什么是参数依赖查找有哪些好的解释?许多人也将其称为 Koenig Lookup。

我最好想知道:

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

What are some good explanations on what argument dependent lookup is? Many people also call it Koenig Lookup as well.

Preferably I'd like to know:

  • Why is it a good thing?
  • Why is it a bad thing?
  • How does it work?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

墟烟 2024-12-22 19:17:10

参数依赖查找 (ADL),有时称为 Koenig Lookup,描述了 C++ 编译器如何查找非限定名称。

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

当函数调用(5.2.2)中的后缀表达式是非限定 ID 时,可以搜索在通常的非限定查找(3.4.1)期间未考虑的其他命名空间,并且在这些命名空间中,命名空间范围的友元可以找到不可见的函数声明(11.3)。对搜索的这些修改取决于参数的类型(对于模板模板参数,模板的命名空间
论证)。

简而言之,Nicolai Josuttis 指出1

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

一个简单的代码示例:

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

MyNamespace::MyClass obj; // global object


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

在上面的示例中,既没有 using 声明,也没有 using 指令,但编译器仍然正确识别非限定名称 doSomething() 作为通过应用 ADL 在命名空间 MyNamespace 中声明的函数。

它是如何运作的?

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

ADL有什么优势?

正如上面的简单代码示例所示,ADL 为程序员提供了便利性和易用性。如果没有 ADL,程序员就会产生开销,需要重复指定完全限定名称,或者使用大量 using 声明。

为什么ADL受到批评?

过度依赖 ADL 可能会导致语义问题,有时会让程序员措手不及。

考虑 std::swap 的示例,其中是交换两个值的标准库算法。使用 ADL 时,必须谨慎使用此算法,因为:

std::swap(obj1,obj2);

可能不会显示与以下行为相同的行为:

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

使用 ADL,调用哪个版本的 swap 函数将取决于传递给的参数的名称空间它。

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

此外,如果由于某种原因 A::swap(A::MyClass&, A::MyClass&)std::swap(A::MyClass&, A::MyClass& ;) 被定义,那么第一个示例将调用 std::swap(A::MyClass&, A::MyClass&) 但第二个示例将无法编译,因为swap(obj1, obj2) 是不明确的。

冷知识:

为什么有时称为“Koenig 查找”?

因为它是由前 AT&T 和贝尔实验室研究员和程序员 Andrew Koenig,尽管柯尼格本人对此提出异议在2012 年的一篇博文

我的名字与参数相关查找相关的原因是,虽然我没有发明这个想法,但我确实认识到命名空间的引入会导致严重的问题,需要 ADL 或类似的东西来解决。< /p>

进一步阅读:



**1** The definition of ADL is as defined in Josuttis' book, *The C++ Standard Library: A Tutorial and Reference*.

Argument Dependent Lookup (ADL), sometimes called Koenig Lookup, describes how unqualified names are looked up by the compiler in C++.

The C++11 standard § 3.4.2/1 states:

When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function declarations (11.3) not otherwise visible may be found. These modifications to the search depend on the types of the arguments (and for template template arguments, the namespace of the template
argument).

In simpler terms Nicolai Josuttis states1:

You don’t have to qualify the namespace for functions if one or more argument types are defined in the namespace of the function.

A simple code example:

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

MyNamespace::MyClass obj; // global object


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

In the above example there is neither a using-declaration nor a using-directive but still the compiler correctly identifies the unqualified name doSomething() as the function declared in namespace MyNamespace by applying ADL.

How does it work?

The algorithm tells the compiler to not just look at local scope, but also the namespaces that contain the argument's type. Thus, in the above code, the compiler finds that the object obj, which is the argument of the function doSomething(), belongs to the namespace MyNamespace. So, it looks at that namespace to locate the declaration of doSomething().

What is the advantage of ADL?

As the simple code example above demonstrates, ADL provides convenience and ease of usage to the programmer. Without ADL there would be an overhead on the programmer, to repeatedly specify the fully qualified names, or instead, use numerous using-declarations.

Why the criticism of ADL?

Over-reliance on ADL can lead to semantic problems, and catch the programmer off guard sometimes.

Consider the example of std::swap, which is a standard library algorithm to swap two values. With the ADL one would have to be cautious while using this algorithm because:

std::swap(obj1,obj2);

may not show the same behavior as:

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

With ADL, which version of swap function gets called would depend on the namespace of the arguments passed to it.

If there exists a namespace A, and if A::obj1, A::obj2, and A::swap() exist, then the second example will result in a call to A::swap(), which might not be what the user wanted.

Further, if for some reason both A::swap(A::MyClass&, A::MyClass&) and std::swap(A::MyClass&, A::MyClass&) are defined, then the first example will call std::swap(A::MyClass&, A::MyClass&) but the second will not compile because swap(obj1, obj2) would be ambiguous.

Trivia:

Why is it sometimes called a “Koenig lookup”?

Because it was devised by former AT&T and Bell Labs researcher and programmer, Andrew Koenig, although Koenig himself disputes this in a 2012 blog post:

The reason that my name is associated with argument-dependent lookup is that although I did not invent the idea, I did recognize that the introduction of namespaces causes a severe problem that ADL, or something similar, was needed to solve.

Further reading:



**1** The definition of ADL is as defined in Josuttis' book, *The C++ Standard Library: A Tutorial and Reference*.

铁憨憨 2024-12-22 19:17:10

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

正是因为有了Koenig Lookup,我们才能这样写:

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

否则,我们就得这样写:

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

这实在是打字太多了,而且代码看起来真的很难看!

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

In Koenig Lookup, if a function is called without specifying its namespace, then the name of a function is also searched in namespace(s) in which the type of the argument(s) is defined. That is why it is also known as Argument-Dependent name Lookup, in short simply ADL.

It is because of Koenig Lookup, we can write this:

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

Otherwise, we would have to write:

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

which really is too much typing and the code looks really ugly!

In other words, in the absence of Koenig Lookup, even a Hello World program looks complicated.

ˉ厌 2024-12-22 19:17:10

也许最好从“为什么”开始,然后再讨论“如何”。

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

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
  ???
}

当然,您可以编写 N::operator++(x),但这会破坏运算符重载的全部意义。因此,必须找到一种解决方案,允许编译器找到 operator++(X&),尽管它不在范围内。另一方面,它仍然不应该找到在另一个不相关的命名空间中定义的另一个 operator++ ,这可能会使调用不明确(在这个简单的示例中,您不会得到歧义,但在更复杂的示例中,你可能会)。解决方案是参数相关查找 (ADL),这样称呼是因为查找取决于参数(更准确地说,取决于参数的类型)。由于该方案是由 Andrew R. Koenig 发明的,因此通常也称为 Koenig 查找。

技巧在于,对于函数调用,除了正常的名称查找(在使用时在范围内查找名称)之外,还会在给定函数的任何参数的类型范围中进行第二次查找。因此,在上面的示例中,如果您在 main 中编写 x++,它不仅会在全局范围内查找 operator++,还会在 x 类型所在的范围内查找 operator++N::X,被定义,即在命名空间N中。在那里它找到了一个匹配的operator++,因此x++就可以工作了。但是,将找不到在另一个命名空间(例如 N2)中定义的另一个 operator++。由于 ADL 不限于运算符,您还可以在 main() 中使用 f(x) 代替 N::f(x)

Maybe it is best to start with the why, and only then go to the how.

When namespaces were introduced, the idea was to have everything defined in namespaces, so that separate libraries don't interfere with each other. However that introduced a problem with operators. Look for example at the following code:

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 operators, you also can use f(x) instead of N::f(x) in main().

柒夜笙歌凉 2024-12-22 19:17:10

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

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

使用 boost 的示例

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

如果用户使用 boost.range 库,这会导致歧义,因为找到了两个 std::begin (通过 ADL 使用 std::vector)并且找到了 boost::begin (通过 ADL 使用 boost::shared_ptr)。

Not everything about it is good, in my opinion. People, including compiler vendors, have been insulting it because of its sometimes unfortunate behavior.

ADL is responsible for a major overhaul of the for-range loop in C++11. To understand why ADL can sometimes have unintended effects, consider that not only the namespaces where the arguments are defined are considered, but also the arguments of template arguments of the arguments, of parameter types of function types / pointee types of pointer types of those arguments, and so on and forth.

An example using boost

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

This resulted in an ambiguity if the user uses the boost.range library, because both std::begin is found (by ADL using std::vector) and boost::begin is found (by ADL using boost::shared_ptr).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文