【问题标题】:c++ ordinary lookup vs argument dependent lookupc ++普通查找与参数相关查找
【发布时间】:2018-01-08 06:46:40
【问题描述】:

考虑到http://en.cppreference.com/w/cpp/language/adl 中描述的这个示例:

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

我想知道为什么会出现歧义,因为如果没有考虑 ADL 规则

“通常的非限定查找产生的查找集包含以下任何一项”。

这里的 B::g 可以通过 http://en.cppreference.com/w/cpp/language/unqualified_lookup 中解释的非限定查找找到,这要归功于规则

对于函数定义中使用的名称,无论是在函数体中还是作为默认参数的一部分,如果函数是用户声明或全局命名空间的成员,则在之前搜索使用该名称的块名称的使用,然后在该块的开始之前搜索封闭块,等等,直到到达作为函数体的块。然后搜索声明函数的命名空间,直到使用该名称的函数的定义(不一定是声明),然后是封闭的命名空间等。

那么我的问题是为什么在这种情况下会考虑 ADL 规则?

【问题讨论】:

  • 您能否详细说明为什么您认为它不应该被考虑在内?
  • 首先,如果通常的非限定查找产生的查找集包含以下任何一项,则不考虑依赖参数的查找:
  • 1) 类成员的声明 2) 块范围内的函数声明(这不是 using 声明) 3) 任何不是函数或函数模板的声明(例如函数对象或名称与正在查找的函数名称冲突的另一个变量)
  • 我在这段代码 sn-p 中看不到这三个。
  • 那么如果通过非限定查找找到的函数找到但不在块范围内,是否考虑ADL规则?

标签: c++ argument-dependent-lookup name-lookup


【解决方案1】:

完整的报价是

首先,如果通常的非限定查找生成的查找集包含以下任何一项,则不考虑参数相关查找:

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

这意味着只有当非限定查找产生上述三个结果之一时,才会忽略 ADL。由于我们不处理类成员,因此函数是在命名空间范围内声明的,而不是在块范围内声明的,我们只找到我们继续使用的函数并使用 ADL。

【讨论】:

  • 那么在块范围内声明函数是什么意思(声明在当前范围内?)
  • @FlashMcQueen 这是一个在block scope声明的函数
  • @NathanOliver 块范围包括全局范围?即使我在全局范围内声明了特定于类型的函数和类型,也会调用特定函数而不是通用函数。
  • @rosshjb 块范围不包括全局范围。那有命名空间范围。如果没有看到您正在使用的代码,我无法说出发生了什么。如果您想澄清,您应该发布问题。
【解决方案2】:

为什么在这种情况下要考虑 ADL(参数相关查找)规则?

因为在关联的命名空间中可能会有更好的匹配。例如:

void f(void*);

namespace A {
struct X;
void f(X*);
}

int main() {
    A::X* x = 0;
    f(x); // A::f is the best match.
}

这种机制常用于swap function

std::swap 可以在命名空间 std 中专门用于用户定义类型,但 ADL 找不到此类专门化(命名空间 std 不是用户定义类型的关联命名空间)。 使用户定义的类型可交换的预期方法是在与类型相同的命名空间中提供非成员函数交换:有关详细信息,请参阅可交换。

std::swap 和用户定义的swap()s 都可见的上下文中,可以使用非限定函数调用swap() 将这种类型的任何左值或右值与其他类型的任何左值或右值交换。

【讨论】:

    【解决方案3】:

    感谢您提出这个问题。我自己来这里寻找答案,我想我已经能够想出一个符合这条规则的例子

    如果由通常的非限定查找生成的查找集包含以下任何一项,则不考虑与参数相关的查找:

    • ...

    • 块范围内的函数声明(不是使用声明)

    并且确实关闭了 ADL。

    #include <iostream>
    
    namespace x {
        struct type {};
        void fn(type) { std::puts("ADL"); }
    }
    
    int main() {
        // Forward function declaration within the function scope.
        // This way "Regular" gets printed out.
        // Comment this declaration out and you'll get "ADL".
        void fn(x::type);
    
        fn(x::type{});
        return 0;
    }
    void fn(x::type) { std::puts("Regular"); }
    

    如果你把这个前置声明放在 ma​​in 之前,你会得到一个模糊调用,因为通常的非限定名称查找和 ADL 都会启动。但是已经声明功能范围内的功能,一切都按承诺进行。

    仍然不确定在野外多久能找到这个。至少,我还没有见过这样的东西,也很难想象这样的编码技巧会在哪里带来好处。

    【讨论】:

      猜你喜欢
      • 2011-03-14
      • 2014-01-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-11-28
      • 1970-01-01
      • 2020-11-20
      • 1970-01-01
      相关资源
      最近更新 更多