Почему std :: find () не использует мой оператор ==? - PullRequest
30 голосов
/ 02 сентября 2011

В следующем фрагменте кода я перегрузил 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')
}

Ответы [ 4 ]

28 голосов
/ 02 сентября 2011

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

Оба аргумента находятся в пространстве имен std (std::pair<std::string, int> и std::string), поэтому ADL начинает с просмотра пространства имен std. Там он находит operator== (какой, это не имеет значения; в стандартной библиотеке есть много, и, если вы включите <string>, может быть найден хотя бы тот, который сравнивает два std::basic_string<T> объекта).

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

17 голосов
/ 02 сентября 2011

Самое чистое решение - создать предикат и использовать 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, вы можете использовать вместо него лямбду.

6 голосов
/ 06 февраля 2018

Принятый ответ, к сожалению, вводит в заблуждение.

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

  1. Регулярный поиск выполняется в соответствии с обычными правилами поиска безоговорочного имени.Это ищется из определения std::find в стандартной библиотеке.Очевидно, что предоставленное пользователем объявление operator == оттуда не видно.

  2. ADL - это отдельная история.Теоретически ADL может видеть имена, определенные позже, например, имена, видимые с точки вызова std::find внутри main.Однако ADL не просто видит все.ADL ограничен поиском только внутри так называемых связанных пространств имен .Эти пространства имен вводятся в рассмотрение по типам аргументов, используемых при вызове оператора == в соответствии с правилами 6.4.2 / 2 .

    . В этом примере типы обоихаргументы == принадлежат пространству имен std.Один аргумент шаблона std:pair<> также взят из std.Другой тип имеет фундаментальный тип int, у которого нет ассоциированного пространства имен .Поэтому std является единственным связанным пространством имен в этом случае.ADL выглядит в std и только в std.Вышеуказанное пользовательское объявление operator == не найдено, поскольку оно находится в глобальном пространстве имен.

    Неверно говорить, что ADL перестает искать после поиска некоторые «другие» определения operator == внутри std.ADL не работает "наизнанку", как часто делают другие формы поиска.ADL ищет в связанных пространствах имен и все.Независимо от того, были ли обнаружены какие-либо другие формы operator == в std или нет, 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 заявлено выше foo.Видя, что bar вызывается с аргументом типа N::S, ADL будет искать bar в связанном пространстве имен N.Там нет bar в N либо.Код плохо сформирован.Обратите внимание, что отсутствие bar в N не заставляет ADL расширять поиск в глобальном пространстве имен и находить глобальный bar.

Довольно просто непреднамеренно изменить набор связанных пространств имен, используемых ADL, поэтому такие проблемы часто возникают и возникают после, казалось бы, невинных и несвязанных изменений в коде.Например, если мы изменим объявление RegPair следующим образом

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

, ошибка внезапно исчезнет.После этого изменения глобальное пространство имен также становится связанным для ADL, наряду с std, поэтому ADL находит предоставленное пользователем объявление operator ==.

1 голос
/ 02 сентября 2011

Другое «правильное» решение:

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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...