Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why isn't std::find() using my operator==?

Tags:

c++

stl

In the following snippet of code, I've overloaded the operator== to compare my pair type with string. But for some reason, the compiler isn't finding my operator as a match for the find function. Why not?

Edit: Thanks for all the suggestions for alternatives, but I'd still like to understand why. The code looks like it should work; I'd like to know why it doesn't.

#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')
}
like image 558
leedm777 Avatar asked Sep 02 '11 17:09

leedm777


People also ask

Does STD find use == operator?

std::find. Returns an iterator to the first element in the range [first,last) that compares equal to val . If no such element is found, the function returns last . The function uses operator== to compare the individual elements to val .

Can we overload== operator in c++?

The overloaded operator must have at least one operands of the user-defined types. You cannot overload an operator working on fundamental types.


3 Answers

The problem is that std::find is a function template and it uses argument-dependent lookup (ADL) to find the right operator== to use.

Both of the arguments are in the std namespace (std::pair<std::string, int> and std::string), so ADL starts by looking in the std namespace. There it finds some operator== (which one, it doesn't matter; there are lots in the Standard Library and if you've included <string>, at least the one that compares two std::basic_string<T> objects could be found).

Because an operator== overload is found in the std namespace, ADL stops searching enclosing scopes. Your overload, which is located in the global namespace, is never found. Name lookup occurs before overload resolution; it doesn't matter during name lookup whether the arguments match.

like image 194
James McNellis Avatar answered Sep 23 '22 11:09

James McNellis


The cleanest solution is to make a predicate and use 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));

If you have C++11 you can use a lambda instead.

like image 33
Kerrek SB Avatar answered Sep 20 '22 11:09

Kerrek SB


The accepted answer is, unfortunately, misleading.

Overload resolution for operator == used inside std::find function template is performed by both regular lookup and argument-dependent lookup (ADL)

  1. Regular lookup is performed in accordance with usual rules of unqualified name lookup. It is looked up from the definition of std::find in standard library. Obviously, the above user-provided declaration of operator == is not visible from there.

  2. ADL is a different story. Theoretically ADL can see names defined later, e.g. names visible from the point of std::find invocation inside main. However, ADL does not just see everything. ADL is restricted to searching only inside so called associated namespaces. These namespaces are brought into the consideration by types of arguments used in the invocation of operator == in accordance to the rules of 6.4.2/2.

    In this example types of both arguments of == belong to namespace std. One template argument of std:pair<> is also from std. Another is of fundamental type int, which has no associated namespace. Therefore std is the only associated namespace in this case. ADL looks in std and only in std. The above user-provided declaration of operator == is not found, since it resides in global namespace.

    It is incorrect to say that ADL stops looking after finding some "other" definitions of operator == inside std. ADL does not work in "inside-out" fashion as other forms of lookup often do. ADL searches in associated namespaces and that's it. Regardless of whether any other forms of operator == were found in std or not, ADL does not attempt to continue its search in global namespace. This is the incorrect/misleading part of the accepted answer.

Here's a more compact example that illustrates the same issue

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);
}

Ordinary lookup fails since there's no bar declared above foo. Seeing that bar is called with an argument of N::S type, ADL will look for bar in associated namespace N. There's no bar in N either. The code is ill-formed. Note that absense of bar in N does not make ADL to expand its search into the global namespace and find global bar.

It is quite easy to inadvertently change the set of associated namespaces used by ADL, which is why such issues often come and go after seemingly innocent and unrelated changes in the code. For example, if we change the declaration of RegPair as follows

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

the error will suddenly disappear. After this change global namespace also becomes associated for ADL, along with std, which is why ADL finds the user-provided declaration of operator ==.

like image 31
AnT Avatar answered Sep 20 '22 11:09

AnT