Невозможно использовать предоставленную пользователем функцию сравнения для std :: set> - PullRequest
0 голосов
/ 25 января 2019

Я наткнулся на очень странную ошибку компиляции для std::set, используя прозрачные компараторы с помощью std::less. Рассмотрим эту простую программу:

using Key = std::string;

bool operator<(const Key&, int) { return true; }
bool operator<(int, const Key&) { return true; }

int main()
{
  std::set<Key, std::less<>> s;
  int x;
  auto it = s.find(x);
}

Это дает мне ошибку компиляции:

error: no matching function for call to object of type 'const std::less<void>'
      if (__j != end() && _M_impl._M_key_compare(__k, _S_key(__j._M_node)))
                          ^~~~~~~~~~~~~~~~~~~~~~

Если я использую свой собственный класс вместо std::string в качестве ключа, он прекрасно работает:

struct My {};
bool operator<(const My&, const My&) { return true; }

using Key = My;

Почему это не работает для std::string?

См. Демо здесь: https://gcc.godbolt.org/z/MY-Y2s

UPD

Что я действительно хотел сделать, так это объявить операторы сравнения между std::unique_ptr<T> и T*. Но я подумал, что будет понятнее с std::string.

1 Ответ

0 голосов
/ 25 января 2019

Поиск в зависимости от аргумента забавная старая вещь, не правда ли?

Уже существует operator<, относящийся к std::string в пространстве имен std, и это обнаруживается при поиске <, который соответствует вашим аргументам. Возможно, нелогично (но не без веской причины), , дальнейший поиск не предпринимается после этого! Другие пространства имен не ищутся. Это даже при том, что только ваша перегрузка фактически соответствует обоим аргументам. Ваш глобальный operator< эффективно скрыт в этом контексте, как показано в следующем ужасающем примере:

namespace N
{
    struct Foo {};

    bool operator<(Foo, Foo) { return false; }
}

bool operator<(N::Foo, int) { return false; }

namespace N
{
    template <typename T1, typename T2>
    bool less(T1 lhs, T2 rhs)
    {
        return lhs < rhs;
    }
}

int main()
{
    N::Foo f;
    N::less(f, 3);
}

/*
main.cpp: In instantiation of 'bool N::less(T1, T2) [with T1 = N::Foo; T2 = int]':
main.cpp:22:17:   required from here
main.cpp:15:20: error: no match for 'operator<' (operand types are 'N::Foo' and 'int')
         return lhs < rhs;
                ~~~~^~~~~
*/

( живая демонстрация )

Теперь вы не можете добавлять вещи в пространство имен std, но это хорошо, потому что было бы намного лучше , если бы вы не перегружали операторы, относящиеся к типам других людей, в любом случае. Это самый быстрый способ скрыть ошибки ODR, когда какая-то другая библиотека делает то же самое.

Точно так же создание чего-то, называемого Key, то есть на самом деле просто скрытого std::string, - это рецепт непреднамеренных конфликтов и неожиданного поведения.

В целом, я бы настоятельно рекомендовал сделать ваш Key как минимум "сильным псевдонимом" для std::string; то есть его собственный тип, а не просто псевдоним. Как вы узнали, это также решает вашу проблему, потому что теперь operator< и тип операнда находятся в одном пространстве имен.

В целом, если вы не действительно используете псевдоним здесь, но действительно хотите работать со стандартными типами, вы вернулись к написанию именованного пользовательского компаратора, который очень хорошо изолирует новую логику и также довольно прост в использовании. Недостатком, конечно, является то, что вы должны каждый раз подписываться на него, но я думаю, что в целом это того стоит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...