Страницы

Поиск по вопросам

воскресенье, 9 февраля 2020 г.

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

#cpp #language_lawyer


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

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

#include 
#include 
#include 
#include 

typedef std::pair RegPair;

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

int main()
{
    std::vector 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==?».
    


Ответы

Ответ 1



Ответ по ссылке, с которой был сделан перевод, неверен/неточен. ADL-поиск никак не заменяет/не исключает обычный поиск, а лишь дополняет его. Правильное описание ситуации заключается в следующем: Обычный поиск выполняется из места вызова оператора == из определения шаблона функции std::find в стандартной библиотеке. Он находит только те имена, которые видны из этого места. Понятно, что оттуда приведенное определение оператора == не видно. 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 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 sequence; S foo("foo"); std::find(sequence.begin(), sequence.end(), foo); } Определение оператора сравнения при этом менять не надо. Код сразу начнет компилироваться и использовать данный оператор сравнения. Другой вариант внешне "невинной" замены, который заставит код компилироваться - сделать второй член пары типом из глобального пространства имен struct X {}; typedef std::pair RegPair; Больше ничего менять не надо - этого уже достаточно для того, чтобы ассоциировать глобальное пространство имен для ADL.

Ответ 2



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

Комментариев нет:

Отправить комментарий