为什么std :: find()不使用我的运算符==?

lee*_*777 30 c++ stl

在下面的代码片段中,我已经重载了operator==以将我的对类型与字符串进行比较.但由于某种原因,编译器没有找到我的运算符作为find函数的匹配项.为什么不?

编辑:感谢所有替代方案的建议,但我仍然想了解原因.代码看起来应该可以工作; 我想知道为什么不这样做.

#include <vector>
#include <utility>
#include <string>
#include <algorithm>

typedef std::pair<std::string, int> RegPair;
typedef std::vector<RegPair> RegPairSeq;

bool operator== (const RegPair& lhs, const std::string& rhs)
{
    return lhs.first == rhs;
}

int main()
{
    RegPairSeq sequence;
    std::string foo("foo");
    // stuff that's not important
    std::find(sequence.begin(), sequence.end(), foo);
    // g++: error: no match for 'operator==' in '__first. __gnu_cxx::__normal_iterator<_Iterator, _Container>::operator* [with _Iterator = std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>*, _Container = std::vector<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int>, std::allocator<std::pair<std::basic_string<char, std::char_traits<char>, std::allocator<char> >, int> > >]() == __val'
    // clang++: error: invalid operands to binary expression ('std::pair<std::basic_string<char>, int>' and 'std::basic_string<char> const')
}
Run Code Online (Sandbox Code Playgroud)

Jam*_*lis 28

问题是它std::find是一个函数模板,它使用依赖于参数的查找(ADL)来查找使用权operator==.

这两个参数都在std命名空间(std::pair<std::string, int>std::string)中,因此ADL首先查看std命名空间.它找到了一些operator==(哪一个,没关系;标准库中有很多,如果你已经包含<string>,至少std::basic_string<T>可以找到比较两个对象的那个).

由于operator==std命名空间中发现了重载,因此ADL会停止搜索封闭的范围.永远不会找到位于全局命名空间中的重载.名称查找在重载解析之前发生; 在名称查找期间,参数是否匹配无关紧要.

  • “因为在‘std’命名空间中发现‘operator==’重载,ADL 停止搜索......”这实际上是不准确的。这里没有“因为”。`std` 是唯一获得 ADL 提名的​​命名空间。所以 ADL 只看那里而不是其他地方。在“std”中是否找到“operator==”的现有重载并不重要。在任何一种情况下,ADL 都不会在“std”之外查找。 (2认同)

Ker*_* SB 17

最干净的解决方案是制作一个谓词并使用find_if:

struct StringFinder
{
  StringFinder(const std::string & st) : s(st) { }
  const std::string s;
  bool operator()(const RegPair& lhs) const { return lhs.first == s; }
}

std::find_if(sequence.begin(), sequence.end(), StringFinder(foo));
Run Code Online (Sandbox Code Playgroud)

如果您有C++ 11,则可以使用lambda.


AnT*_*AnT 6

不幸的是,接受的答案具有误导性.

函数模板==内部使用的运算符的重载分辨率std::find由常规查找和参数依赖查找(ADL)执行

  1. 根据通常的非限定名称查找规则执行常规查找.它从std::find标准库的定义中查找.显然,operator ==从那里看不到上面用户提供的声明.

  2. ADL是一个不同的故事.从理论上讲,ADL可以看到后面定义的名称,例如从std::find内部调用点可见的名称main.但是,ADL并不只是看到了一切.ADL仅限于在所谓的关联命名空间内搜索.==根据6.4.2/2的规则,运算符调用中使用的参数类型会考虑这些名称空间.

    在此示例中,==属于命名空间的两个参数的类型std.一个模板参数std:pair<>也来自std.另一种是基本类型int,没有关联的命名空间.因此std,在这种情况下,唯一关联的命名空间.ADL std只会查看std.operator ==找不到上面用户提供的声明,因为它驻留在全局命名空间中.

    说ADL停止寻找operator ==内部的一些"其他"定义是不正确的std.ADL不像其他形式的查找那样以"由内而外"的方式工作.ADL搜索关联的命名空间,就是这样.无论是否operator ==发现任何其他形式std,ADL都不会尝试继续在全局命名空间中进行搜索.这是接受的答案中不正确/误导的部分.

这是一个更紧凑的例子,说明了同样的问题

namespace N
{
  struct S {};
}

template<typename T> void foo(T a) 
{
  bar(a);
}

void bar(N::S s) {}

int main()
{
  N::S a;
  foo(a);
}
Run Code Online (Sandbox Code Playgroud)

普通查找失败,因为bar上面没有声明foo.看到bar使用N::S类型的参数调用它,ADL将bar在关联的命名空间中查找N.有没有barN任.代码格式不正确.请注意,absense barin in N不会使ADL将其搜索扩展到全局命名空间并查找全局bar.

很容易无意中更改ADL使用的关联命名空间集合,这就是为什么这些问题经常出现在代码中看似无辜和无关的更改之后.例如,如果我们更改声明RegPair如下

enum E { A, B, C };
typedef std::pair<std::string, E> RegPair;
Run Code Online (Sandbox Code Playgroud)

错误会突然消失.在此更改之后,全局命名空间也与ADL相关联std,这就是ADL找到用户提供的声明的原因operator ==.