Унаследованный синтезированный оператор сравнения выдает предупреждение / ошибку: ISO C 20 рассматривает использование перегруженного оператора ‘!=’

#c #c 20 #spaceship-operator

#c #c 20 #космический корабль-оператор

Вопрос:

В следующем фрагменте кода clang 11.0.1 генерирует предупреждение

 template <class T>
struct iterator_facade
{
    template<class S>
    bool operator==(const S amp;other) const noexcept
    {
        return static_cast<const T amp;>(*this).equal_to(other);
    }
};

struct iterator : public iterator_facade<iterator>
{
    bool equal_to(const iterator amp;) const noexcept
    {
        return true;
    }
};

bool check(iterator a, iterator b)
{
    return a == b;
}

 

Код работает:
https://godbolt.org/z/65zWEq

 source>:21:14: warning: ISO C  20 considers use of overloaded operator '==' (with operand types 'iterator' and 'iterator') to be ambiguous despite there being a unique best viable function [-Wambiguous-reversed-operator]
    return a == b;
           ~ ^  ~
<source>:5:7: note: ambiguity is between a regular call to this operator and a call with the argument order reversed
        bool operator==(const S amp;other) const noexcept
             ^
 

Приведенный выше код успешно компилируется с Visual C (VS 16.8.x) и с предыдущим предварительным просмотром (VS 16.9.0 Preview 2). Однако недавно выпущенный предварительный просмотр версии 16.9.0 3 теперь выдает ошибку для этого фрагмента кода:

 1>C:MyProjectstesttestsource.cpp(21,16): error C2666: 'foo<bar>::operator ==': 2 overloads have similar conversions
1>C:MyProjectstesttestsource.cpp(5,7): message : could be 'bool iterator_facade<iterator>::operator ==<bar>(const S amp;) noexcept const' [rewritten expression '!(x == y)']
1>        with
1>        [
1>            S=iterator
1>        ]
1>C:MyProjectstesttestsource.cpp(5,7): message : or 'bool iterator_facade<iterator>::operator ==<iterator>(const S amp;) noexcept const' [synthesized expression '!(y == x)']
1>        with
1>        [
1>            S=iterator
1>        ]
1>C:MyProjectstesttestsource.cpp(21,16): message : while trying to match the argument list '(iterator, iterator)'
 

Похоже ли, что не существует совместимого способа предоставления синтезированных операторов сравнения для производного класса iterator с классом CRTP iterator_facade ?

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

1. Весь смысл синтезированных операторов заключается в том, что это достаточно легко сделать, и вам не нужно делать это в базовом классе. Просто предоставление bar operator== перегрузки, точно эквивалентной equal_to , должно быть в порядке.

2. Этот пример в любом случае не был бы скомпилирован на C 17. У вас есть более полный пример того, что вы имеете в виду? foo Предполагается, что он также поддерживает гетерогенное сравнение или просто однородное сравнение?

3. Это упрощение следующей iterator_facade реализации здесь: https://github.com/vector-of-bool/neo-fun/blob/develop/src/neo/iterator_facade.hpp Весь смысл этого класса в том, чтобы эти операторы были реализованы в базовом классе 🙂

4. Этот класс фасада итератора пытается реализовать требуемые операторы сравнения итераторов с помощью производного класса ‘ equal_to или distance_to членов, в зависимости от того, что доступно. Более старые версии реализовывали обычные ==, != и так далее. Через некоторое время автор переключился на синтезированные операторы C 20, упростив код. И раньше он работал до версии 16.9.0 Preview 3. При этом он начал генерировать ошибку, которую я упростил до приведенного выше фрагмента кода.

Ответ №1:

Проблема в том, что у нас есть этот оператор сравнения:

 template<class T>
struct iterator_facade
{
    template <class S>
    bool operator==(const S amp;other) const noexcept;
};
 

Итак, когда мы пытаемся сравнить два iterator s, у нас есть эти два кандидата:

 bool operator==(iterator_facade<iterator> constamp;, iterator constamp;); // the member candidate
bool operator==(iterator constamp;, iterator_facade<iterator> constamp;); // the reversed member candidate
 

И проблема в том, что кандидат на член является точным совпадением во 2-м аргументе, но преобразованием производного в базовое в первой паре … а обратный кандидат является точным совпадением в 1-м аргументе, но преобразованием производного в базовое во втором. Это означает, что ни один из кандидатов не лучше другого, и оба они неоднозначны.

Теперь, поскольку для начала этой библиотеке действительно требуется C 20, это бессмысленно. Просто сделайте iterator реализацию operator== самостоятельно, это ничего не добавляет? Я все равно не уверен, что он добавляет.

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

 template <class T>
struct iterator_facade
{
    friend bool operator==(T constamp; a, T constamp; b) noexcept {
        return a.equal_to(b);
    }

    template <sentinel_for<T> S>
    bool operator==(S constamp; other) const noexcept;
};
 

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

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

1. А, понятно. Наличие оператора сравнения друзей с точными двумя типами фактически устраняет двусмысленность. Большое тебе спасибо, Барри!

2. Хм, но cppreference говорит, что «Разрешение перегрузки в этом случае имеет окончательный тай-брейк, предпочитающий непереписанных кандидатов переписанным кандидатам» , что означает, что не должно быть никакой двусмысленности?

3. Тай-брейки @HolyBlackCat вступают в игру только в том случае, если последовательности преобразования равны (см. Раздел «наилучшая жизнеспособная функция» там).