Почему операторы switch и if ведут себя по-разному с операторами преобразования?

Почему операторы switch и if ведут себя по-разному с операторами преобразования?

struct WrapperA
{
    explicit operator bool() { return false; }    
};

struct WrapperB
{
    explicit operator int() { return 0; }
};

int main()
{
    WrapperA wrapper_a;
    if (wrapper_a) { /** this line compiles **/ }

    WrapperB wrapper_b;
    switch (wrapper_b) { /** this line does NOT compile **/ }
}

Ошибка компиляции - это switch quantity is not an integer а в выражении if оно отлично распознается как bool. (ССЗ)

Ответ 1

Синтаксис - это switch ( condition ) statement с

условие - любое выражение целочисленного или перечисляемого типа или типа класса, контекстно неявно конвертируемого в интегральный или перечисляемый тип, или объявление одной переменной без массива такого типа с инициализатором скобки или равно.

Взято из cppreference.

Это означает, что вы можете делать только случай переключения для целочисленного или перечисляемого типа. Чтобы компилятор мог неявно преобразовывать Wrapper в тип integer/enum, вам нужно удалить явное ключевое слово:

Явный спецификатор указывает, что конструктор или функция преобразования (поскольку С++ 11) не допускают неявных преобразований

Вы также можете перевести Wrapper в тип int.

Отредактируйте адрес @acraig5075:

Вы должны быть осторожны, оператор явно и неявный. Если оба они неявные, код не будет компилироваться, потому что будет неопределенность:

struct Wrapper
{
    operator int() { return 0; }
    operator bool() { return true; }    
};

source_file.cpp: В функции 'int main(): source_file.cpp: 12: 14:

ошибка: неоднозначное преобразование типа по умолчанию из 'Wrapper

переключатель (w) {

^ source_file.cpp: 12: 14: примечание: преобразование кандидата

включить 'Wrapper :: operator int() и' Wrapper :: operator bool()

Единственный способ устранить двусмысленность - сделать актерский состав.

Если только один из операторов является явным, другой будет выбран для оператора switch:

#include <iostream>
struct Wrapper
{
    explicit operator int() { return 0; }
    operator bool() { return true; }    
};

int main()
{
    Wrapper w;
    if (w) { /** this line compiles **/std::cout << " if is true " << std::endl; }
    switch (w) { 
        case 0:
            std::cout << "case 0" << std::endl;
            break;
        case 1:
            std::cout << "case 1" << std::endl;
            break;
    }
    return 0;
}

Выход:

 if is true 
case 1

w неявно преобразован в 1 (true) (поскольку оператор int является явным) и выполняется случай 1.

С другой стороны:

struct Wrapper
{
    operator int() { return 0; }
    explicit operator bool() { return true; }    
};

Ouput:

 if is true 
case 0

w неявно преобразован в 0 потому что оператор bool является явным.

В обоих случаях утверждение if истинно, потому что w оценивается контекстуально с булевым внутри оператора if.

Ответ 2

Я думаю, это объясняет, почему оператор switch не принимается, тогда как оператор if:

В следующих пяти контекстах ожидается тип bool и неявная последовательность преобразования строится, если декларация bool t(e); хорошо сформирована. то есть явная определяемая пользователем функция преобразования, такая как explicit T::operator bool() const; Считается. Такое выражение e называется контекстно конвертируемым в bool.

  • контролируя выражение if, while, for;
  • логические операторы!, && и ||;
  • условный оператор?:;
  • static_assert;
  • noexcept.

Ответ 3

Один ответ заключается в том, что if и switch ведет себя по-другому, потому что это то, как был написан стандарт. Другой ответ мог бы предположить, почему стандарт был написан именно так. Ну, я полагаю, что стандарт сделал, if утверждения ведут себя таким образом, чтобы решить конкретную проблему (неявные преобразования в bool были проблематичными), но я хотел бы принять другую точку зрения.

В выражении if условие должно быть логическим значением. Не все думают о том, являются if утверждения таким образом, по-видимому, из-за различных удобств, встроенных в язык. Однако, по сути, оператор if должен знать "сделать это" или "сделать это"; "да или нет"; true или false - т.е. булево значение. В этом отношении, помещая что-то внутри оператора условно, явно запрашивает, чтобы что-то было преобразовано в bool.

С другой стороны, оператор switch принимает любой интегральный тип. То есть, нет единого типа, предпочтительного для всех остальных. Использование switch можно рассматривать как явный запрос на преобразование значения в интегральный тип, но не обязательно специально для int. Таким образом, не считается целесообразным использовать преобразование в int когда требуется конкретное преобразование.

Ответ 4

Объявление оператора преобразования explicit существует для предотвращения неявных преобразований этого типа. Это его цель. switch пытается неявно преобразовывать свой аргумент в целое число; поэтому explicit оператор не будет вызываться. Это ожидаемое поведение.

Неожиданным является то, что explicit оператор вызывается в случае if. И тем самым повесил сказку.

См., Учитывая приведенные выше правила, способ сделать тип, проверяемый через if, чтобы сделать explicit преобразование non- в bool. Дело в том, что... bool - проблемный тип. Он неявно конвертируется в целые числа. Поэтому, если вы создаете тип, который неявно конвертируется в bool, это легальный код:

void foo(int);
foo(convertible_type{});

Но это тоже бессмысленный код. Вы никогда не имели в виду для convertible_type для неявного преобразования в целое число. Вы просто хотели, чтобы он конвертировался в логическое для целей тестирования.

Pre-С++ 11, способ исправить это была "безопасная идиома bool", которая была сложной болью и не имела логического смысла (в основном, вы предоставляли неявное преобразование в указатель-член, который был конвертирован в логическое, но а не целые или обычные указатели).

Поэтому в С++ 11, когда они добавили explicit операторы преобразования, они сделали исключение для bool. Если у вас есть explicit operator bool(), этот тип может быть "контекстно преобразован в bool " внутри заданного количества языковых мест. Это позволяет explicit operator bool() означать "проверяемый в булевых условиях".

switch не нуждается в такой защите. Если вы хотите, чтобы тип неявно конвертировался в int для целей switch, нет никаких причин, по которым он не был бы неявным образом конвертируется в int для других целей.

Ответ 5

У вас есть две проблемы с вашим кодом. Во-первых, оператор преобразования не должен быть явным для работы с операторами switch. Во-вторых, условие switch требует интегрального типа, и оба типа int и bool являются такими типами, поэтому существует двусмысленность. Если вы измените свой класс, чтобы он не имел этих двух проблем, switch будет компилироваться и работать должным образом.

Другое решение, которое не требует изменения вашего класса, - это явно использовать (static_cast будет делать) значение для int или bool.

Ответ 6

Я считаю, что реальная причина такого поведения уходит корнями в C, но прежде чем объяснять это, я попытаюсь оправдать его в C++.

Оператор if/while/for должен принимать любой скаляр (целое число, float или указатель) или экземпляр класса, конвертируемый в bool. Он просто проверяет, соответствует ли значение нулю. Учитывая эту вседозволенность, относительно безвредно для компилятора использовать explicit оператор для соответствия значения в выражении if.

switch, с другой стороны, действительно имеет смысл с целыми числами и enum. Если вы попытаетесь использовать switch с double или указателем, вы получите ту же ошибку. Это упрощает определение дискретных значений. Поскольку оператор switch специально должен видеть целое число, вероятно, ошибка использования экземпляра класса, который не определяет неявное преобразование.

Исторически, причина в том, что это так, как это делает C.

C++ изначально предполагалось, что он будет обратно совместим с C (хотя в этом он никогда не был успешным). C никогда не имел логического типа до более поздних времен. C, if утверждения должны быть разрешительными, потому что просто не было другого способа сделать это. В то время как C не выполняет преобразования типа struct-to-scalar типа C++, C++ обработка explicit методов отражает тот факт, что if утверждения очень разрешимы на обоих языках.

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