Зачем переключаться и если операторы ведут себя по-разному с операторами преобразования?

Почему 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с другой стороны, имеет смысл только с целыми числами и enums. Если вы попытаетесь использовать 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,

Другие вопросы по тегам