Ответ 1
Важные моменты:
Во-первых, есть две релевантные перегрузки operator <
.
-
operator <(const Foo&, const Foo&)
. Использование этой перегрузки требует пользовательского преобразования литерала0
вFoo
с помощьюFoo(int)
. -
operator <(int, int)
. Использование этой перегрузки требует преобразованияFoo
вbool
с пользовательскимoperator bool()
, за которым следует продвижение доint
(это стандартизировано, отличное от преобразования, как было указано Бо Перссоном).
Вопрос здесь: откуда возникает двусмысленность? Конечно, первый вызов, который требует только пользовательского преобразования, более разумен, чем второй, для чего требуется пользовательское преобразование, за которым следует продвижение?
Но это не так. Стандарт присваивает ранг каждому кандидату. Тем не менее, нет никакого ранга для "пользовательского преобразования, за которым следует продвижение". Это имеет тот же ранг, что и только с использованием пользовательского преобразования. Просто (но неформально), ранжирующая последовательность выглядит примерно так:
- точное соответствие
- (только) требуется продвижение
- (только) требуется неявное преобразование (включая "небезопасные", наследуемые от C, такие как
float
доint
) - требуется определенное пользователем преобразование
(Отказ от ответственности: как уже упоминалось, это неформально. Он становится значительно более сложным, когда задействованы несколько аргументов, и я также не упоминал ссылки или cv-квалификацию. Это просто предназначено в качестве приблизительного обзора.)
Итак, это, надеюсь, объясняет, почему вызов неоднозначен. Теперь о практической части того, как это исправить. Почти никогда не кто-то, кто предоставляет operator bool()
, хочет, чтобы он неявно использовался в выражениях, включающих целочисленную арифметику или сравнения. В С++ 98 были неясные обходные пути, начиная от std::basic_ios<CharT, Traits>::operator void *
до "улучшенных" более безопасных версий, включая указатели на членов или неполные частные типы. К счастью, С++ 11 представил более читаемый и последовательный способ предотвращения цельной рекламы после неявного использования operator bool()
, который должен обозначить оператор как explicit
. Это полностью удалит перегрузку operator <(int, int)
, а не просто "понизит ее".
Как уже упоминалось, вы можете также отметить конструктор Foo(int)
как явный. Это приведет к обратному эффекту удаления перегрузки operator <(const Foo&, const Foo&)
.
Третьим решением будет обеспечение дополнительных перегрузок, например:
-
operator <(int, const Foo&)
-
operator <(const Foo&, int)
Последний, в этом примере, будет предпочтительнее вышеупомянутых перегрузок как точное совпадение, даже если вы не представили explicit
. То же самое происходит, например, для
-
operator <(const Foo&, long long)
который был бы предпочтительнее operator <(const Foo&, const Foo&)
в a < 0
, потому что для его использования требуется только продвижение.