【问题标题】:How does ADL affect this piece of C++ code?ADL 如何影响这段 C++ 代码?
【发布时间】:2020-12-09 10:45:15
【问题描述】:

实际上,下面的代码并不能用 Clang 用这个命令编译:

clang++ -std=c++11 test.cc -o test.

我只是想模仿与 C++ 中的“交换习惯用法”相同的行为,以使用“使用指令”来启用 ADL。但是下面的代码我哪里错了?预期的调用优先级应该是:N1::foo > N2::foo > ::foo,对吧?

namespace N1 {
  struct S {};
  void foo(S s) {
    std::cout << "called N1::foo.";
  }
}
namespace N2 {
  void foo(N1::S s) {
    std::cout << "called N2::foo.";
  }
}
void foo(N1::S s) {
  std::cout << "called foo.";
}
int main() {
  using N2::foo;  
  foo(N1::S{});
}

错误信息:

test.cc:54:3: error: call to 'foo' is ambiguous
  foo(N1::S{});
  ^~~
test.cc:40:8: note: candidate function
  void foo(S s) {
       ^
test.cc:45:8: note: candidate function
  void foo(N1::S s) {
       ^
1 error generated.

更新:

我将 N2::foo 更改为可以在某种程度上模仿 std::swap 的模板方法。那么,这里的问题是为什么::foo不能被main函数中的“foo(N1::S{});”调用?由于该函数应该比在具有相同优先级时调用的模板函数更合适。

namespace N1 {
  struct S {};
  /*
  void foo(S s) {
    std::cout << "called N1::foo, specific one." << '\n';
  }
  */
}
namespace N2 {  // as a fallback to unqualified name which has no user-defined overload.
  template<typename T>
  void foo(T) {
    std::cout << "called N2::foo, generic one." << '\n';
  }
}
void foo(N1::S s) {
  std::cout << "called foo." << '\n';
}
int main() {
  using N2::foo;
  foo(N1::S{});
  foo(10);  // use generic version.
}

【问题讨论】:

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


    【解决方案1】:

    在这种情况下,normal name lookup 找到N2::fooN1::fooADL 找到,它们都被添加到重载集,然后执行重载解析,调用不明确。

    顺便说一句:如果main() 中没有using N2::foo;,则::foo 将通过正常名称查找找到,而N1::foo 也可以通过ADL 找到;结果调用仍然模棱两可。

    更新:

    那么,这里的问题是为什么::foo不能被main函数中的“foo(N1::S{});”调用?

    因为using N2::foo;的使用,N2::foo这个名字被引入到main函数中。当调用foo 时,名称N2::foo 将在main 的范围内找到,然后名称查找停止,不会检查进一步的范围(全局名称空间),因此不会找到::foo 并且完全添加到重载集。结果N2::foo 被两种情况调用。

    名称查找检查scopes,如下所述,直到找到至少一个任何类型的声明,此时查找停止并且不再检查范围。

    顺便说一句:如果您将using N2::foo; 放在main 之前的全局命名空间中,foo(N1::S{}); 将调用::fooN2::foo::foo 都可以通过名称查找找到,::foo 在重载解析中获胜。

    LIVE

    【讨论】:

    • 那么,在没有这个问题的“交换习语”中,using-directive(使用std::swap)是如何工作的呢?
    • @YHSPY 你能做一个最小的例子来说明std::swap的问题吗?
    【解决方案2】:

    首先,您有普通的查找,从内部范围搜索到外部范围,并在第一次匹配时停止,从后面的范围隐藏重载。然后,当 ADL 被触发时,它会在搜索中添加额外的关联实体和命名空间。

    因此,在您的情况下,ADL 查找不会向重载集添加任何内容。

    #include <iostream>
    
    namespace N1 {
      struct S {};
      /*
      void foo(S s) {
        std::cout << "called N1::foo, specific one." << '\n';
      }
      */
    }
    namespace N2 { 
      template<typename T>
      void foo(T) {
        std::cout << "called N2::foo, generic one." << '\n';
      }
    }
    void foo(N1::S s) {
      std::cout << "called foo." << '\n';
    }
    int main() {
      using N2::foo;
      foo(N1::S{}); // ordinary lookup finds N2 in main scope and stops.
                    // adl lookup add N1 ns to the additionnal ns set but finds nothing
                    // overload set = N2::foo by ordinary lookup
    }
    

    现在,如果我取消注释您的 S1 版本,它将获胜!这就是你得到的:

    #include <iostream>
    
    namespace N1 {
      struct S {};
      void foo(S s) {
        std::cout << "called N1::foo, specific one." << '\n';
      }
    }
    namespace N2 { 
      template<typename T>
      void foo(T) {
        std::cout << "called N2::foo, generic one." << '\n';
      }
    }
    void foo(N1::S s) {
      std::cout << "called foo." << '\n';
    }
    int main() {
      using N2::foo;
      foo(N1::S{}); // ordinary lookup finds N2 in main scope and stops
                    // adl lookup add N1 ns to the additionnal ns set and finds N1::foo
                    // overload set = N2::foo by ordinary lookup, N1::foo by ADL, N1::foo wins
    }
    

    你用交换得到同样的东西。在这种情况下,使用 Foo::swap 是因为它位于 N1 命名空间中:

    #include <iostream>
    
    namespace N1 {
        struct Foo {
    
        };
        void swap(Foo& , Foo&) {
            std::cout << "swap Foo" << std::endl;
        }
    }
    
    namespace N2 {
    
    struct S {
        N1::Foo f;
    };
    
    void swap(S& l,S& r) {
        using std::swap; // overload set is std::swap by ordinary lookup
        swap(l.f, r.f); // and Foo::swap by ADL, Foo::swap wins
    }
    }
    
    int main() {
        N2::S s1,s2;
        swap(s1,s2);
    }
    

    但是,如果您在全局 ns 中移动 Foo 特定交换,则 std::swap 会被调用:

    #include <iostream>
    
    namespace N1 {
        struct Foo {
    
        };
    }
    
    void swap(N1::Foo& , N1::Foo&) {
        std::cout << "swap Foo" << std::endl;
    }
    
    namespace N2 {
    
    struct S {
        N1::Foo f;
    };
    
    void swap(S& l,S& r) {
        using std::swap; // overload set is std::swap by ordinary lookup
        swap(l.f, r.f); // because ADL does not add the global ns to the
                        // ns to be searched for
    }
    }
    
    int main() {
        N2::S s1,s2;
        swap(s1,s2);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-12-05
      • 2020-11-29
      • 2017-04-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-14
      相关资源
      最近更新 更多