为什么std::find()没有使用我的operator==?

32
在下面的代码片段中,我已经重载了operator==来比较我的对类型和字符串。但是由于某种原因,编译器没有将我的运算符作为查找函数的匹配项。为什么? 编辑: 感谢所有提出替代方案的建议,但我仍然想了解 为什么。代码看起来应该可以工作;我想知道为什么不行。
#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')
}

2
你为什么不使用 std::map 呢? - sbi
2
@sbi:这是一个简单的例子,基于在更大的代码库中找到的东西。还有其他限制条件使我们想要使用一对向量。 - leedm777
2
@dave:我知道这样做有其原因(事实上,我过去也曾这样做),但考虑到我们在这里得到的问题类型,我认为至少要问一下。 - sbi
1
不要使用typedef,而是创建自己的类:class RegPair: public std::pair<std::string, int> {}; 由于你的类现在在全局命名空间中,它将无法找到你版本的operator==。你当前的失败原因是typedef实际上不是typedef,它是类型别名(即类型的另一个名称)。因此,在ADL中它没有帮助。 - Martin York
1
@dave, @sbi:你们可能会对boost::container::flat_map<>感兴趣,它来自于最近正式接受的Boost.Containers库。我相信该库预计将从1.49开始包含在官方的Boost发行版中。 - ildjarn
4个回答

29
问题在于std::find是一个函数模板,它使用参数依赖查找(ADL)来找到正确的operator==
两个参数都在std命名空间中(std::pair<std::string, int>std::string),所以ADL从std命名空间开始查找。在那里,它找到了一些operator==(哪个不重要;在标准库中有很多,如果你已经包含了<string>,至少可以找到比较两个std::basic_string<T>对象的那个)。
由于在std命名空间中找到了一个operator==重载,ADL停止搜索封闭的范围。你的重载,在全局命名空间中,从未被发现。名称查找发生在重载决议之前;在名称查找期间,参数是否匹配都不重要。

2
这实际上是不准确的。这里没有“因为”。std是唯一指定用于ADL的命名空间,所以ADL只在那里查找,而不会查找其他地方。无论std中是否存在现有的operator==重载,都没有关系。在任何情况下,ADL都不会在std之外查找。 - AnT stands with Russia

17

最清晰的解决方案是创建一个谓词(predicate)并使用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));

如果您使用的是C++11,您可以使用lambda代替。


1
我被你领先了54秒!我给你点赞。 - sbi
1
是的 - 这就是我们最终所做的,使用lambda表达式会使它变得更棒。但我仍然很好奇为什么std::find没有找到我的operator== - leedm777
问题在于您的设置中没有任何自定义类型。如果您有,比如说 RegPair = std::pair<std::string, Foo>,那么就没问题了。只有在至少一个类型是自定义类型时才能进行重载。 - Kerrek SB

9
很遗憾,被接受的答案是误导性的。在 std::find 函数模板内部使用的运算符 == 的重载解析是通过常规查找和参数相关查找(ADL)同时进行的。
  1. 按照未限定名称查找的通常规则进行正常查找。它从标准库中的 std::find 的定义中查找。显然,上述用户提供的 operator == 声明在那里是不可见的。

  2. ADL 是另一回事。理论上,ADL 可以看到后面定义的名称,例如从 main 中调用 std::find 点处可见的名称。但是,ADL 不会看到所有内容。ADL 仅限于在所谓的 相关命名空间 中搜索。这些命名空间是根据使用运算符 == 调用时参数类型的规则引入考虑的,遵循 6.4.2/2 规则。

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

    说 ADL 在找到一些 std 内部的其他定义的 operator == 后停止查找是不正确的。ADL 不像其他形式的查找那样以“从内向外”的方式工作。ADL 在相关命名空间中搜索,就这样。无论是否在 std 中找到了任何其他形式的 operator ==,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);
}

普通的查找失败,因为上面没有声明bar。由于bar被调用时带有一个N::S类型的参数,ADL将在相关联的命名空间N中查找bar。但是在N中没有bar。代码不规范。请注意,N中缺少bar并不会使ADL扩展其搜索到全局命名空间并找到全局的bar

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

enum E { A, B, C };
typedef std::pair<std::string, E> RegPair;

错误将突然消失。在此更改后,全局命名空间也与std一起关联到ADL中,这就是为什么ADL找到了用户提供的operator ==声明的原因。

1

另一个“正确”的解决方案:

struct RegPair : std::pair<std::string, int>
{
    bool operator== (const std::string& rhs) const;
};

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

3
此解决方案缺乏说明为什么它有效。请注意,问题询问的是为什么它是一个问题,而不是如何修复它。 - Rob Kennedy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接