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

时间:2011-09-02 17:37:34

标签: 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')
}

4 个答案:

答案 0 :(得分:28)

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

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

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

答案 1 :(得分: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));

如果你有C ++ 11,你可以使用lambda。

答案 2 :(得分:6)

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

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

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

  2. ADL是另一回事。从理论上讲,ADL可以看到后面定义的名称,例如:从std::find内的main调用点可以看到名称。但是,ADL并不只是看到了一切。 ADL仅限于在所谓的associated namespaces内搜索。根据{{​​3}}的规则,调用运算符==时使用的参数类型会考虑这些名称空间。

    在此示例中,==的两个参数的类型都属于名称空间stdstd:pair<>的一个模板参数也来自std。另一种是基本类型int,其6.4.2/2。因此std是这种情况下唯一关联的命名空间。 ADL仅在std中查看std。找不到上面用户提供的operator ==声明,因为它位于全局命名空间中。

    说ADL在寻找其他&#34;其他&#34;之后停止寻找是不正确的。 operator ==std的定义。 ADL不适用于&#34;由内到外&#34;时尚就像其他形式的查找一样。 ADL搜索关联的命名空间及其相关的命名空间。无论是否在operator ==中找到任何其他形式的std,ADL都不会尝试继续在全局命名空间中进行搜索。这是接受的答案中不正确/误导的部分。

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

    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以上没有声明foo。看到使用bar类型的参数调用N::S,ADL将在关联的命名空间bar中查找Nbar中也没有N。代码格式不正确。请注意,bar中忽略N不会使ADL将其搜索范围扩展到全局命名空间并查找全局bar

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

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

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

答案 3 :(得分: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;
}