Зачем переключаться и если операторы ведут себя по-разному с операторами преобразования?
Почему 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
, (ССЗ)
6 ответов
Синтаксис switch ( condition ) statement
с
условие - любое выражение целочисленного или перечислимого типа, или типа класса, контекстуально неявно преобразуемого в целочисленный или перечислимый тип, или объявление единственной переменной, не являющейся массивом, такого типа с инициализатором скобки или равенства.
Взято из cppreference.
Это означает, что вы можете переключать регистр только на целочисленный или перечислимый тип. Чтобы компилятор мог неявно конвертировать Wrapper в тип integer / enum, вам нужно удалить явное ключевое слово:
Явный спецификатор указывает, что конструктор или функция преобразования (начиная с C++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'
switch (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.
Я думаю, это объясняет, почему switch
заявление не принято, тогда как if
утверждение является:
В следующих пяти контекстах ожидается тип bool и последовательность неявного преобразования создается, если объявление
bool t(e);
хорошо сформирован. то есть явная пользовательская функция преобразования, такая какexplicit T::operator bool() const;
Считается. Такое выражение e называется контекстно-конвертируемым в bool.
- управление выражением if, while, for;
- логические операторы!, && и ||;
- условный оператор?:;
- static_assert;
- noexcept.
Объявление оператора конвертации explicit
существует для предотвращения неявных преобразований в этот тип. Это его цель. switch
пытается неявно преобразовать свой аргумент в целое число; поэтому explicit
Оператор не будет вызываться. Это ожидаемое поведение.
Что является неожиданным, так это то, что explicit
Оператор вызывается в if
дело. И тем самым висит сказка.
Смотрите, учитывая приведенные выше правила, способ сделать тип тестируемым через if
это сделать неexplicit
преобразование в bool
, Дело в том... bool
это проблемный тип. Он неявно конвертируется в целые числа. Так что если вы делаете тип, который неявно преобразуется в bool
, это юридический код:
void foo(int);
foo(convertible_type{});
Но это тоже бессмысленный код. Ты никогда не хотел convertible_type
неявно преобразовать в целое число. Вы просто хотели преобразовать его в логическое значение для тестирования.
До C++11, способ исправить это был "безопасный булевский идиом", который был сложной болью и не имел никакого логического смысла (в основном, вы предоставили неявное преобразование в указатель на член, который был преобразован в логическое значение, но не на целые числа или обычные указатели).
Так в C++11, когда они добавили explicit
операторы преобразования, они сделали исключение для bool
, Если у вас есть explicit operator bool()
этот тип может быть "контекстно преобразован в bool
"внутри заданного количества определенных языком мест. Это позволяет explicit operator bool()
означать "тестируемый в булевых условиях".
switch
не нуждается в такой защите. Если вы хотите, чтобы тип был неявно преобразован в int
за switch
целей, нет никаких причин, по которым он не будет неявно преобразован в int
и для других целей.
Один ответ таков: if
а также switch
вести себя по-другому, потому что так был написан стандарт. Другой ответ может дать предположение о том, почему стандарт был написан таким образом. Ну, я полагаю, стандарт сделал if
заявления ведут себя таким образом, чтобы решить конкретную проблему (неявные преобразования в bool
было проблематично), но я хотел бы принять другую точку зрения.
В if
утверждение, условие должно быть логическим значением. Не все думают о if
заявления таким образом, предположительно из-за различных удобств, встроенных в язык. Тем не менее, по своей сути, if
утверждение должно знать "делай это" или "делай то"; "да или нет"; true
или же false
то есть логическое значение. В этом отношении, помещая что-то в условный оператор, явно запрашивается преобразование чего-либо в bool
,
С другой стороны, switch
Оператор принимает любой целочисленный тип. То есть, нет единого предпочтительного типа по сравнению с другими. Использование switch
можно рассматривать как явный запрос на преобразование значения в целочисленный тип, но необязательно специально для int
, Поэтому нецелесообразно использовать преобразование в int
когда это конкретное преобразование должно быть явно запрошено.
Я полагаю, что истинная причина такого поведения коренится в C, но прежде чем объяснить это, я попытаюсь объяснить это в терминах C++.
if
/while
/for
оператор должен принимать любой скаляр (целое число, число с плавающей точкой или указатель) или экземпляр класса, конвертируемый в bool
, Он просто проверяет, равно ли значение нулю. Учитывая эту вседозволенность, для компилятора относительно безопасно использовать explicit
оператор, чтобы вписать значение в if
заявление.
switch
с другой стороны, имеет смысл только с целыми числами и enum
s. Если вы попытаетесь использовать switch
с double
или указатель, вы получите ту же ошибку. Это облегчает определение значений отдельных случаев. Так как switch
В операторе особенно необходимо видеть целое число, вероятно, было бы ошибкой использовать экземпляр класса, который не определяет неявное преобразование.
Исторически причина в том, что именно так С это делает.
Изначально C++ был предназначен для обратной совместимости с C (хотя в этом он никогда не был успешным). C никогда не имел логического типа до более поздних времен. C о if
заявления должны были быть разрешительными, потому что просто не было другого способа сделать это. В то время как C не выполняет преобразования типа структура в скаляр, как это делает C++, C++ рассматривает explicit
методы отражают тот факт, что if
заявления очень разрешительны на обоих языках.
C о switch
утверждение, в отличие от if
, должен работать с дискретными значениями, поэтому он не может быть таким разрешающим.
Есть две проблемы с вашим кодом. Во-первых, операторы преобразования не должны быть явными для работы с switch
заявления. Во-вторых, switch
условие требует целочисленного типа и оба int
а также bool
Есть такие типы, так что есть двусмысленность. Если вы измените свой класс так, чтобы у него не было этих двух проблем, switch
скомпилирует и будет работать как положено.
Другое решение, которое не требует изменения вашего класса, это явное приведение (static_cast
будет делать) значение для int
или же bool
,