#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 вступают в игру только в том случае, если последовательности преобразования равны (см. Раздел «наилучшая жизнеспособная функция» там).