ADL 未按预期工作

ADL not working as expected

struct S
{
    vector<int> v;
    void method()
    {
        begin(v);
    }
};

上面的代码片段编译得很好,因为 ADL 直到我添加

auto begin() { return begin(v); }

到 class 声明。在这一点上,C++ 忘记了 ADL,而是更喜欢 S::begin,它甚至没有可行的重载,从而产生错误

error: no matching function for call to ‘S::begin(std::vector<int>&)’ begin(v);

有什么办法可以解决这个问题吗?我在问,因为在阅读 Why use non-member begin and end functions in C++11? 之后,我开始在各处使用 begin()end() 自由函数以保持一致性,但现在我在定义自己的 begin()end() 方法。

如评论中所述 S::begin 隐藏 std::begin。您可以通过键入 using std::begin 或显式调用 std::begin.

std::begin 引入 S 的范围
struct S
{
    std::vector<int> v;
    void method()
    {
        using std::begin;
        begin(v);
    }

    auto begin() {  using std::begin; return begin(v); }
};

您正在使用 begin 作为非限定名称,以及非限定名称查找

... examines the scopes as described below, until it finds at least one declaration of any kind, at which time the lookup stops and no further scopes are examined.

reference.

从您的成员函数的角度来看,第一个提供名称 begin 的作用域是 class 作用域,因此它从该作用域填充重载集,然后停止查找。

只有在这个名称查找阶段之后,它才会尝试选择一个重载集,并决定没有任何匹配项。编译器不会返回并从下一个范围开始搜索,它只是放弃。

您的选择是:

  1. 使用现有的容器成员函数而不是自由函数(这比下面的明确限定版本稍微不那么冗长)

    auto begin() { return v.begin(); }
    
  2. 改用限定名称

    auto begin() { return ::std::begin(v); }
    
  3. 将正确的重载添加到 class 范围,using std::begin;

    忽略那个,我忘了你不能在 class 范围内使用 using.

  4. 引入非成员名称
  5. 将正确的重载注入到成员函数体本身的较窄范围内,因此搜索到此为止

    auto begin() { using std::begin; return begin(v); }
    
  6. 首先停止提供 begin 成员函数,而是将非成员 begin 重载添加到封闭的命名空间。这更现代并且避免了查找问题。

    namespace N {
      struct S { std::vector<int> v; };
      std::vector<int>::iterator begin(S& s) { return s.v.begin(); }
      // and end, cbegin, etc. etc.
    }