Подтвердить что ты не робот

Почему это выражение С++, связанное с перегруженными операторами и неявное преобразование, неоднозначно?

operator bool прерывает использование operator< в следующем примере. Может ли кто-нибудь объяснить, почему bool столь же актуально в выражении if (a < 0), что и в конкретном операторе, есть ли обходной путь?

struct Foo {
    Foo() {}
    Foo(int x) {}

    operator bool() const { return false; }

    friend bool operator<(const Foo& a, const Foo& b) {
        return true;
    }
};

int main() {
    Foo a, b;
    if (a < 0) {
        a = 0;
    }
    return 1;
}

Когда я компилирую, я получаю:

g++ foo.cpp
foo.cpp: In function 'int main()':
foo.cpp:18:11: error: ambiguous overload for 'operator<' (operand types are 'Foo' and 'int')
     if (a < 0) {
           ^
foo.cpp:18:11: note: candidate: operator<(int, int) <built-in>
foo.cpp:8:17: note: candidate: bool operator<(const Foo&, const Foo&)
     friend bool operator<(const Foo& a, const Foo& b)
4b9b3361

Ответ 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, потому что для его использования требуется только продвижение.

Ответ 2

Проблема в том, что С++ имеет два варианта решения a < 0:

  • Преобразуйте a в bool и сравните результат с 0 со встроенным оператором < (одно преобразование)
  • Преобразуйте 0 в Foo и сравните результаты с <, которые вы определили (одно преобразование)

Оба подхода эквивалентны компилятору, поэтому он выдает ошибку.

Вы можете сделать это явным, удалив преобразование во втором случае:

if (a < Foo(0)) {
    ...
}

Ответ 3

Поскольку компилятор не может выбирать между bool operator <(const Foo &,const Foo &) и operator<(bool, int), которые подходят в этой ситуации.

Чтобы исправить проблему, сделайте второй конструктор explicit:

struct Foo
{
    Foo() {}
    explicit Foo(int x) {}

    operator bool() const { return false; }

    friend bool operator<(const Foo& a, const Foo& b)
    {
        return true;
    }
};

Edit: Хорошо, наконец, я получил реальную точку вопроса:) OP спрашивает, почему его компилятор предлагает operator<(int, int) в качестве кандидата, хотя "многоступенчатые преобразования не допускаются" .

Ответ: Да, для вызова operator<(int, int) объект a необходимо преобразовать Foo -> bool -> int. Но, С++ Standard фактически не говорит, что "многоступенчатые преобразования являются незаконными".

§ 12.3.4 [class.conv]

Не более одного пользовательского преобразования (конструктор или преобразование функция) неявно применяется к одному значению.

bool до int не определяется пользователем, поэтому он является законным, а компилятор имеет полное право выбрать operator<(int, int) в качестве кандидата.

Ответ 4

Это именно то, что говорит вам компилятор.

Один подход для решения if (a < 0) для компилятора заключается в использовании конструктора Foo(int x), который вы предоставили, для создания объекта с 0.

Второй вариант - использовать преобразование operator bool и сравнить его с int (продвижение). Подробнее об этом можно узнать в разделе Числовые рекламные акции.

Следовательно, для компилятора он неоднозначен, и он не может решить, к какому пути вы его хотите.