Этот пример LLVM сокращается до:
struct iterator;
struct const_iterator {
const_iterator(iterator const&);
};
struct iterator {
bool operator==(const_iterator const&) const;
bool operator!=(const_iterator const&) const;
};
bool b = iterator{} != iterator{};
В C ++ 17 это нормально: у нас есть только один кандидат, и он жизнеспособен (iterator
конвертируется в const_iterator
, так что один работает ).
В C ++ 20 у нас неожиданно появилось три кандидата. Я собираюсь выписать их с использованием синтаксиса, не являющегося членом, поэтому параметры более очевидны:
bool operator==(iterator const&, const_iterator const&); // #1
bool operator==(const_iterator const&, iterator const&); // #2 (reversed #1)
bool operator!=(iterator const&, const_iterator const&); // #3
#2
- обратный кандидат для #1
. Обратного кандидата для #3
нет, поскольку только основные операторы сравнения (==
и <=>
) получают обращенных кандидатов.
Теперь первым шагом в разрешении перегрузки является выполнение последовательностей преобразования. У нас есть два аргумента типа iterator
: для #1
это точное совпадение / преобразование. Для #2
это конверсия / точное совпадение. Для #3
это точное совпадение / конверсия. Проблема здесь в том, что у нас есть «триггер» между #1
и #2
: каждый лучше в одной паре параметр / аргумент и хуже в другой. Это неоднозначно. Даже если в каком-то смысле #3
является «лучшим кандидатом», мы не добьемся столь далеко - неоднозначная последовательность преобразования означает неоднозначное разрешение перегрузки.
Теперь, g cc компилирует это в любом случае (я не совсем уверен, какие именно правила c он здесь реализует), и даже clang даже не считает это ошибкой, просто предупреждением (который вы можете отключить с помощью -Wno-ambiguous-reversed-operator
). В настоящее время ведется работа по более изящному разрешению этих ситуаций.
Чтобы быть немного более полезным, вот более прямое сокращение примера LLVM вместе с тем, как мы могли бы исправить это для C ++ 20:
template <bool Const>
struct iterator {
using const_iterator = iterator<true>;
iterator();
template <bool B, std::enable_if_t<(Const && !B), int> = 0>
iterator(iterator<B> const&);
#if __cpp_impl_three_way_comparison >= 201902
bool operator==(iterator const&) const;
#else
bool operator==(const_iterator const&) const;
bool operator!=(const_iterator const&) const;
#endif
};
В C ++ 20 мы только нужен один, однородный оператор сравнения.
Это не сработало в C ++ 17 из-за желания поддержать случай iterator<false>{} == iterator<true>{}
: единственный кандидат там iterator<false>::operator==(iterator<false>)
, и Вы не можете конвертировать const_iterator
в iterator
.
Но в C ++ 20 это нормально, потому что в этом случае теперь у нас есть два кандидата: оператор равенства iterator<false>
и iterator<true>
оператор обратного равенства. Первый не жизнеспособен, но последний работает и работает нормально.
Нам также нужен только operator==
. operator!=
мы просто получаем бесплатно, так как все, что нам нужно, это отрицание равенства.
В качестве альтернативы, вы все еще в C ++ 17 можете написать операторы сравнения как скрытые друзья:
friend bool operator==(iterator const&, iterator const&);
friend bool operator!=(iterator const&, iterator const&);
Что дает вам то же поведение, что и в C ++ 20, благодаря участию кандидатов от обоих типов (просто нужно написать одну дополнительную функцию по сравнению с версией C ++ 20, и эта функция должна быть скрытой друг - он не может быть членом).