Почему std::find не использует мой operator==?

284
07 февраля 2018, 13:38

Я реализовал свою перегрузку operator== для сравнения своего std::pair<...> с std::string. Но по какой-то причине компилятор не может найти эту перегрузку. С чем это может быть связано?

Код для воспроизведения ошибки:

#include <algorithm>
#include <string>
#include <utility>
#include <vector>
typedef std::pair<std::string, int> RegPair;
bool operator==(const RegPair& lhs, const std::string& rhs)
{
    return lhs.first == rhs;
}
int main()
{
    std::vector<RegPair> sequence;
    std::string foo("foo");
    std::find(sequence.begin(), sequence.end(), foo);
}

Текст ошибки:

  • GNU GCC:

    error: no match for 'operator==' in '__first. __gnu_cxx::__normal_iterator<_Iterator, _Container>::operator* with _Iterator = std::pair, std::allocator >, int>*, _Container = std::vector, std::allocator >, int>, std::allocator, std::allocator >, int> > > == __val'

  • clang:

    error: invalid operands to binary expression ('std::pair, int>' and 'std::basic_string const')

Данный вопрос является свободным переводом «Why isn't std::find() using my operator==?».

Answer 1

Ответ по ссылке, с которой был сделан перевод, неверен/неточен. ADL-поиск никак не заменяет/не исключает обычный поиск, а лишь дополняет его.

Правильное описание ситуации заключается в следующем:

  1. Обычный поиск выполняется из места вызова оператора == из определения шаблона функции std::find в стандартной библиотеке. Он находит только те имена, которые видны из этого места.

    Понятно, что оттуда приведенное определение оператора == не видно.

  2. ADL-поиск выполняется в ассоциированных и только в ассоциированных пространствах имен и видит эти пространства имен такими, каким они стали на момент вызова функции std::find в вызывающем коде. Набор ассоциированных пространств имен строится в соответствии с правилами, описанными 6.4.2/2.

    В данном случае из точки вызова std::find приведенное определение оператора == прекрасно видно. Но это определение сделано в глобальном пространстве имен. А ассоциированным для ADL в данном случае является только пространство std, ибо оба аргумента сравнения принадлежат пространству std. Поэтому глобальное пространство имен не рассматривается ADL и данное определение не находится.

    Утверждение о том, что ADL якобы прекращает дальнейший поиск определений operator == именно из-за того, что какие-то определения operator == уже найдены внутри std - неверно. ADL всегда ищет имена только внутри ассоциированных пространств имен. В отличие от обычного lookup, ADL никогда не расширяет область поиска за пределы ассоциированных пространств имен, независимо от того, найдено там что-либо или нет.

Ту же проблему можно проиллюстрировать следующим маленьким примером

namespace N
{
  struct S {};
}
template <typename T> void foo(T a) 
{
  bar(a);                         // 1
}
void bar(N::S s) {}
int main()
{
  N::S a;
  foo(a);                         // 2
}

При таком порядке объявлений обычный поиск имен находит имена, видные из точки 1, а ADL поиск находит имена, видные из точки 2, но только в ассоциированных пространствах имен. Глобальное пространство имен ассоциированным не является, поэтому объявление void bar(N::S s) не находится и код не компилируется.

В исходном варианте, если мы каким-то образом "притянем за уши" глобальное пространство имен в качестве ассоциированного для ADL, то данный оператор == сразу начнет находиться через ADL. Например, объявим в глобальном пространстве имен некий фиктивный тип, приводимый к std::string и используем именно его в качестве ключа для поиска

...
struct S : std::string
{
    using std::string::string;
};
int main()
{
    std::vector<RegPair> sequence;
    S foo("foo");
    std::find(sequence.begin(), sequence.end(), foo);
}

Определение оператора сравнения при этом менять не надо. Код сразу начнет компилироваться и использовать данный оператор сравнения.

Другой вариант внешне "невинной" замены, который заставит код компилироваться - сделать второй член пары типом из глобального пространства имен

struct X {};
typedef std::pair<std::string, X> RegPair;

Больше ничего менять не надо - этого уже достаточно для того, чтобы ассоциировать глобальное пространство имен для ADL.

Answer 2

Проблема заключается в том, что std::find является шаблонной функцией, а потому при поиске operator== в дело вступает ADL (поиск, зависимый от типов аргументов).

Оба аргумента функции (std::pair и std::string) находятся в одном и том же пространстве имён (::std), поэтому ADL начинает поиск именно в нём. При этом достаточно, чтобы там был определён хоть какой-то operator==, так как сопоставление имён (name lookup) производится до определения подходящих перегрузок.

В связи с тем, что подходящий по имени operator== обязательно будет найден (в том же<string> объявлен как минимум оператор сравнения std::string с чем-то), алгоритм останавливает свою работу. До вашей же перегрузки, расположенной в глобальном пространстве имён (то есть за пределами ::std), очередь так и не дойдёт.

READ ALSO
Ошибка при генерации случайных чисел

Ошибка при генерации случайных чисел

Пытаюсь сделать рандом, используя c++11Делаю так:

247
Когда пишу if (Serial.read() == &#39;qwerty&#39;) arduino не правильно понимает

Когда пишу if (Serial.read() == 'qwerty') arduino не правильно понимает

Тут вроде все норм, а снизу неочень

267
ошибка компиляции при использовании std::bind2nd

ошибка компиляции при использовании std::bind2nd

Очень долго старался понять в чем же ошибка, когда я использую std::bind2nd

212
Путь к файлу программы без консоли. С++

Путь к файлу программы без консоли. С++

Всем привет, мне нужно узнать программно где лежат файл программы

277