命名空间
变体
操作

依赖于参数的查找

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
 
 

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

依赖于参数的查找使得在不同的命名空间中使用运算符成为可能。示例

#include <iostream>
 
int main()
{
    std::cout << "Test\n"; // There is no operator<< in global namespace, but ADL
                           // examines std namespace because the left argument is in
                           // std and finds std::operator<<(std::ostream&, const char*)
    operator<<(std::cout, "Test\n"); // Same, using function call notation
 
    // However,
    std::cout << endl; // Error: “endl” is not declared in this namespace.
                       // This is not a function call to endl(), so ADL does not apply
 
    endl(std::cout); // OK: this is a function call: ADL examines std namespace
                     // because the argument of endl is in std, and finds std::endl
 
    (endl)(std::cout); // Error: “endl” is not declared in this namespace.
                       // The sub-expression (endl) is not an unqualified-id
}

内容

[编辑] 详细信息

首先,如果通常的未限定名称查找产生的查找集中包含以下任何一项,则不考虑依赖于参数的查找

1) 类成员声明。
2) 块级函数声明(不是using 声明)。
3) 任何不是函数或函数模板的声明(例如,函数对象或其名称与正在查找的函数名称冲突的另一个变量)。

否则,对于函数调用表达式中的每个参数,都会检查其类型以确定与其关联的命名空间和类集,这些命名空间和类将被添加到查找中。

1) 对于基本类型参数,关联的命名空间和类集为空。
2) 对于类类型参数(包括联合),该集包含
a) 该类本身。
b) 如果该类是完整的,则包括其所有直接和间接基类。
c) 如果该类是另一个类的成员,则包括其所属的类。
d) 添加到该集的类的最内层封闭命名空间。
3) 对于其类型为类模板特化的参数,除了类规则之外,还会将以下关联类和命名空间添加到该集中。
a) 为类型模板参数提供的所有模板参数的类型(跳过非类型模板参数,并跳过模板模板参数)。
b) 任何模板模板参数所属的命名空间。
c) 任何模板模板参数所属的类(如果它们恰好是类成员模板)。
4) 对于枚举类型参数,会将定义枚举类型的声明的最内层封闭命名空间添加到该集中。如果枚举类型是类的成员,则会将该类添加到该集中。
5) 对于类型为指向 T 的指针或指向 T 数组的指针的参数,会检查类型 T 并将其关联的类和命名空间集添加到该集中。
6) 对于函数类型参数,会检查函数参数类型和函数返回类型,并将它们关联的类和命名空间集添加到该集中。
7) 对于类型为指向类 X 的成员函数 F 的指针的参数,会检查函数参数类型、函数返回类型和类 X,并将它们关联的类和命名空间集添加到该集中。
8) 对于类型为指向类 X 的数据成员 T 的指针的参数,会检查成员类型和类型 X,并将它们关联的类和命名空间集添加到该集中。
9) 如果参数是重载函数集(或函数模板)的名称或 取地址表达式,则会检查重载集中的每个函数,并将与其关联的类和命名空间集添加到该集合中。
  • 此外,如果重载集是由 模板标识符 命名,则会检查其所有类型模板参数和模板模板参数(但不会检查非类型模板参数),并将与其关联的类和命名空间集添加到该集合中。

如果关联的类和命名空间集中存在任何命名空间为 内联命名空间,则也会将其封闭的命名空间添加到该集合中。

如果关联的类和命名空间集中存在任何命名空间直接包含内联命名空间,则会将该内联命名空间添加到该集合中。

(自 C++11 起)

在确定关联的类和命名空间集之后,为了进行进一步的 ADL 处理,将丢弃此集中所有类中找到的声明,但第 2 点中提到的命名空间范围的友元函数和函数模板除外。

通过普通的 非限定查找 找到的声明集和通过 ADL 在关联集的所有元素中找到的声明集将合并,并遵循以下特殊规则

1) 关联命名空间中的 using 指令会被忽略。
2) 声明在关联类中的命名空间范围的友元函数(和函数模板)即使通过普通的查找不可见,也能通过 ADL 被看见。
3) 除函数和函数模板外,所有名称都会被忽略(不会与变量冲突)。

[编辑] 注意

由于参数依赖查找,与类位于同一命名空间中的非成员函数和非成员运算符被视为该类的公共接口的一部分(如果它们通过 ADL 找到)[2].

ADL 是通用代码中用于交换两个对象的既定惯例背后的原因:using std::swap; swap(obj1, obj2);,因为直接调用 std::swap(obj1, obj2) 不会考虑可能与 obj1obj2 类型位于同一命名空间中的用户定义的 swap() 函数,而仅仅调用非限定的 swap(obj1, obj2) 如果没有提供用户定义的重载,则什么也不会调用。特别是,std::iter_swap 和所有其他标准库算法在处理 可交换 类型时使用这种方法。

名称查找规则使得在全局命名空间或用户定义的命名空间中声明操作符来操作来自 std 命名空间的类型变得不切实际,例如,operator>>operator+ 用于 std::vectorstd::pair(除非向量/对的元素类型是用户定义的类型,这会将它们的命名空间添加到 ADL)。这些操作符不会从模板实例化中查找,例如标准库算法。有关更多详细信息,请参阅 依赖名称

ADL 可以找到一个 友元函数(通常是重载运算符),该函数完全定义在类或类模板内,即使它从未在命名空间级别声明过。

template<typename T>
struct number
{
    number(int);
    friend number gcd(number x, number y) { return 0; }; // Definition within
                                                         // a class template
};
 
// Unless a matching declaration is provided gcd is
// an invisible (except through ADL) member of this namespace
void g()
{
    number<double> a(3), b(4);
    a = gcd(a, b); // Finds gcd because number<double> is an associated class,
                   // making gcd visible in its namespace (global scope)
//  b = gcd(3, 4); // Error; gcd is not visible
}

尽管即使普通查找没有找到任何内容,函数调用也能通过 ADL 解析,但对具有显式指定模板参数的 函数模板 的函数调用要求通过普通查找找到模板的声明(否则,遇到未知名称后跟小于号就是一个语法错误)。

namespace N1
{
    struct S {};
 
    template<int X>
    void f(S);
}
 
namespace N2
{
    template<class T>
    void f(T t);
}
 
void g(N1::S s)
{
    f<3>(s);     // Syntax error until C++20 (unqualified lookup finds no f)
    N1::f<3>(s); // OK, qualified lookup finds the template 'f'
    N2::f<3>(s); // Error: N2::f does not take a non-type parameter
                 //        N1::f is not looked up because ADL only works
                 //              with unqualified names
 
    using N2::f;
    f<3>(s); // OK: Unqualified lookup now finds N2::f
             //     then ADL kicks in because this name is unqualified
             //     and finds N1::f
}
(在 C++20 之前)

在以下上下文中,仅执行 ADL 查找(即,仅在关联命名空间中查找)

  • 范围 for 循环在成员查找失败时执行的非成员函数 beginend 的查找。
(自 C++11 起)
(自 C++17 起)

[编辑] 示例

来自 http://www.gotw.ca/gotw/030.htm 的示例

namespace A
{
    struct X;
    struct Y;
 
    void f(int);
    void g(X);
}
 
namespace B
{
    void f(int i)
    {
        f(i); // Calls B::f (endless recursion)
    }
 
    void g(A::X x)
    {
        g(x); // Error: ambiguous between B::g (ordinary lookup)
              //        and A::g (argument-dependent lookup)
    }
 
    void h(A::Y y)
    {
        h(y); // Calls B::h (endless recursion): ADL examines the A namespace
              // but finds no A::h, so only B::h from ordinary lookup is used
    }
}

[编辑] 缺陷报告

以下行为更改缺陷报告被追溯应用到先前发布的 C++ 标准。

DR 应用于 已发布的行为 正确行为
CWG 33 C++98 关联的命名空间或类未指定
如果用于查找的参数是
一组重载函数或函数模板的地址
已指定
CWG 90 C++98 嵌套非联合类的关联类
不包括其封闭类,但嵌套
联合与封闭类相关联
非联合也相关联
CWG 239 C++98 在普通
非限定查找中找到的块范围函数声明没有阻止 ADL 发生
ADL 未被考虑,除了
using 声明
CWG 997 C++98 依赖参数类型和返回类型被
排除在确定函数模板的关联
类和命名空间之外
已包含
CWG 1690 C++98
C++11
ADL 无法找到返回的 lambda(C++11)或
局部类类型对象(C++98)
可以找到它们
CWG 1691 C++11 ADL 对不透明枚举声明具有意外行为 已修复
CWG 1692 C++98 双重嵌套类没有关联命名空间
(它们的封闭类不是任何命名空间的成员)
关联命名空间被
扩展到最内层的
封闭命名空间
CWG 2857 C++98 不完整
类类型的关联类包括其基类
不包含

[编辑] 另请参阅

[编辑] 外部链接

 
  1. Andrew Koenig: "关于参数依赖查找的个人说明"
  2. H. Sutter (1998) "什么是类? - 接口原则" 在 C++ 报告中,10(3)