it-roy-ru.com

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

Почему операторы 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. (ССЗ)

15
nyarlathotep108

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

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

Взято из cppreference.

Это означает, что вы можете использовать регистр переключения только для целого числа или перечисления type. Чтобы компилятор мог неявно конвертировать 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:

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

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.

17
Clonk

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

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

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

Объявление оператора преобразования 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 для других целей.

3
Nicol Bolas

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

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

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

3
JaMiT

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

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

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

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

Изначально C++ был предназначен для обратной совместимости с C (хотя в этом он никогда не был успешным). C никогда не имел логического типа до более поздних времен. Заявления С if должны были быть разрешающими, потому что просто не было другого способа сделать это. В то время как C не выполняет преобразования типа структура в скаляр, как это делает C++, обработка C++ методов explicit отражает тот факт, что операторы if очень разрешительны в обоих языках.

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

0
luther

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

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

0
navyblue