Перечисления C ++ подписаны или неподписаны?


107

Перечисления C ++ подписаны или неподписаны? Кроме того, безопасно ли проверять ввод, проверяя, что это <= ваше максимальное значение, и не учитывать> = ваше минимальное значение (при условии, что вы начали с 0 и увеличили на 1)?


Когда мы используем тип перечисления в контексте, который требует его знака, мы фактически говорим о неявном преобразовании перечисления в целочисленный тип. В стандарте C ++ 03 сказано, что это делается с помощью Integral Promotion, ничего не связанного с базовым типом перечисления. Итак, я не понимаю, почему каждый ответ здесь упоминает, что базовый тип не определен стандартом? Я описал ожидаемое поведение здесь: stackoverflow.com/questions/24802322/…
JavaMan

Ответы:


60

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

Вкратце: вы не можете полагаться на то, что перечисление подписано или неподписано.


28
Ответ Майкла Берра (который цитирует стандарт) на самом деле подразумевает, что вы можете положиться на его подпись, если вы определяете значение перечисления как отрицательное из-за того, что тип может «представлять все значения перечислителя, определенные в перечислении».
Сэмюэл Хармер

101

Пойдем к первоисточнику. Вот что говорится в документе стандарта C ++ 03 (ISO / IEC 14882: 2003) в 7.2-5 (Объявления перечисления):

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

Короче говоря, ваш компилятор может выбирать (очевидно, если у вас есть отрицательные числа для некоторых значений перечисления, они будут подписаны).


Как мы можем избежать предположений компилятора и указать ему использовать базовый беззнаковый тип, когда все значения перечисления являются небольшими положительными целыми числами? (Мы ловим вывод UBsan, потому что компилятор выбирает int, а int страдает от переполнения. Значение беззнаковое и положительное, и наше использование зависит от беззнакового обтекания, чтобы обеспечить декремент или «отрицательный шаг»).
jww

@jww - это будет зависеть от того, какой именно компилятор вы используете, я думаю. Поскольку стандарт не определяет базовый тип и оставляет это на усмотрение реализации, необходимо просмотреть документацию по вашему инструменту и посмотреть, возможен ли вообще этот вариант. Если вы хотите гарантировать определенное поведение в своем коде, почему бы не привести член перечисления, который вы используете в выражении?
ysap

22

Вы не должны зависеть от того, подписаны они или нет. Если вы хотите сделать их явно подписанными или неподписанными, вы можете использовать следующее:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

11
Только в будущем стандарте C ++ 0x.
далле

3
@dalle Компилятор Microsoft также позволяет типизированные перечисления msdn.microsoft.com/en-us/library/2dzy4k6e(v=vs.80).aspx
teodozjan 08

15

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

В C ++ 0x будут добавлены строго типизированные перечисления , которые позволят вам указать тип перечисления, например:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

Однако даже сейчас можно выполнить простую проверку, используя перечисление в качестве типа переменной или параметра, например:

enum Fruit { Apple, Banana };

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.

Думаю, ваш второй пример немного сбивает с толку :)
Miral

5

Компилятор может решить, подписаны ли перечисления или нет.

Другой метод проверки перечислений - использовать само перечисление как тип переменной. Например:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};

enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.

5

Даже некоторые старые ответы получили 44 положительных голоса, я склонен не соглашаться со всеми из них. Короче говоря, я не думаю, что нам следует заботиться о underlying typeперечислении.

Во-первых, тип Enum в C ++ 03 - это отдельный тип, не имеющий понятия знака. Поскольку из стандарта C ++ 03dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

Поэтому, когда мы говорим о знаке типа перечисления, скажем, при сравнении двух операндов перечисления с использованием <оператора, мы фактически говорим о неявном преобразовании типа перечисления в некоторый целочисленный тип. Важен только знак этого интегрального типа . И при преобразовании enum в целочисленный тип применяется следующее утверждение:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

И, по-видимому, основной тип перечисления не имеет ничего общего с Integral Promotion. Поскольку стандарт определяет интегральное продвижение следующим образом:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

Итак, становится ли перечисление типом signed intили unsigned intзависит от того, signed intможет ли он содержать все значения определенных перечислителей, а не базовый тип перечисления.

См. Мой связанный вопрос Признак неправильного типа перечисления C ++ после преобразования в интегральный тип


Это имеет значение, когда вы компилируете с помощью -Wsign-conversion. Мы используем его, чтобы выявлять непреднамеренные ошибки в нашем коде. Но +1 за ссылку на стандарте, и указывая на то , что перечисление не имеет типа ( по signedсравнению unsigned) , связанный с ним.
jww

4

В будущем с C ++ 0x будут доступны строго типизированные перечисления, которые будут иметь несколько преимуществ (например, безопасность типов, явные базовые типы или явное определение области видимости). Благодаря этому вы могли бы лучше быть уверены в знаке типа.


4

В дополнение к тому, что другие уже говорили о подписанном / неподписанном, вот что стандарт говорит о диапазоне перечислимого типа:

7.2 (6): «Для перечисления, где e (min) - наименьший перечислитель, а e (max) - наибольший, значения перечисления - это значения базового типа в диапазоне от b (min) до b (max ), где b (min) и b (max) - соответственно наименьшее и наибольшее значения наименьшего битового поля, которое может хранить e (min) и e (max). Можно определить перечисление, значения которого не определены любым из его счетчиков ".

Так например:

enum { A = 1, B = 4};

определяет перечислимый тип, где e (min) равно 1, а e (max) равно 4. Если базовый тип подписан int, то наименьшее требуемое битовое поле имеет 4 бита, и если целые числа в вашей реализации являются двумя дополнениями, тогда допустимый диапазон перечисление составляет от -8 до 7. Если базовый тип беззнаковый, то он имеет 3 бита, а диапазон - от 0 до 7. Если вас это интересует, проверьте документацию по компилятору (например, если вы хотите привести целые значения, отличные от перечислителей, к перечислимого типа, тогда вам нужно знать, находится ли значение в диапазоне перечисления или нет - если нет, результирующее значение перечисления не указано).

Вопрос о том, являются ли эти значения допустимыми входными данными для вашей функции, может отличаться от того, являются ли они допустимыми значениями перечисляемого типа. Ваш проверочный код, вероятно, беспокоится о первом, а не о втором, поэтому в этом примере следует, по крайней мере, проверять> = A и <= B.


0

Проверьте это с помощью std::is_signed<std::underlying_typeперечислений + scoped enums по умолчаниюint

https://en.cppreference.com/w/cpp/language/enum подразумевает:

main.cpp

#include <cassert>
#include <iostream>
#include <type_traits>

enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};

int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;

    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));

    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub вверх по течению .

Скомпилируйте и запустите:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

Вывод:

0

Проверено на Ubuntu 16.04, GCC 6.4.0.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.