Чи підписані або непідписані C ++ перерахунки?


107

Чи підписані або непідписані C ++ перерахунки? А внаслідок розширення чи безпечно перевірити вхід, перевіривши, що він <= ваше максимальне значення, і залиште> = ваше мінімальне значення (якщо припустити, що ви почали з 0 і збільшили на 1)?


Коли ми використовуємо тип enum у контексті, який вимагає його ознаки, ми фактично говоримо про перетворення enum в інтегральний тип неявно. Стандарт C ++ 03 говорить, що це робиться інтегральною промоцією, нічого, що стосується базового типу перерахунку. Отже, я не розумію, чому кожна відповідь тут зазначає, що базовий тип не визначається стандартом? Я описав очікуваний behaviou тут: stackoverflow.com/questions/24802322 / ...
JavaMan

Відповіді:


60

Не слід покладатися на якесь конкретне представництво. Прочитайте наступне посилання . Також стандарт говорить, що визначено реалізацією, який інтегральний тип використовується як базовий тип для enum, за винятком того, що він не повинен бути більшим за int, якщо якесь значення не може вміститися в int або неподписаний int.

Коротше кажучи: ви не можете розраховувати на те, що enum буде підписаний або непідписаний.


28
Відповідь Майкла Берра (яка цитує стандарт) насправді означає, що ви можете розраховувати на те, що воно буде підписане, якщо ви визначите значення перерахунку як негативне через те, що тип може "представляти всі значення перелічувача, визначені в перерахунку".
Самуель Хармер

101

Перейдемо до джерела. Ось що йдеться у документі стандарту C ++ 03 (ISO / IEC 14882: 2003) у 7.2-5 (декларації перерахування):

Основний тип перерахунку - це інтегральний тип, який може представляти всі значення перерахунку, визначені в перерахунку. Визначено реалізацією, який інтегральний тип використовується як базовий тип для перерахування, за винятком того, що базовий тип не повинен бути більшим за int, якщо значення перелічувача не може вміститися в int або неподписаний 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

15

Ви не повинні покладатися на те, що він підписаний або непідписаний. Згідно стандарту визначається реалізацією, який інтегральний тип використовується як базовий тип для перерахунку. Однак у більшості реалізацій це ціле підписане число.

У C ++ 0x будуть додані сильно набрані перерахування , які дозволять вам вказати тип перерахунку, такий як:

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

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

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перелік.

По-перше, C ++ 03 тип Enum - це окремий тип, який не має поняття знака. Оскільки від C ++ 03 стандартdcl.enum

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

Отже, коли ми говоримо про знак типу enum, скажімо, порівнюючи 2 операнди enum за допомогою <оператора, ми фактично говоримо про неявне перетворення типу enum в якийсь інтегральний тип. Саме ознака цього інтегрального типу має значення . І при перетворенні enum в цілісний тип застосовується це твердження:

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

І, мабуть, базовий тип перерахунку не має нічого спільного з інтегральним просуванням. Оскільки стандарт визначає інтегральну акцію так:

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.

Отже, чи стає тип enum signed intчи unsigned intзалежить від того, чи signed intможуть містити всі значення визначених нумераторів, а не базовий тип enum.

Дивіться моє відповідне запитання Знак типу C ++ Enum Неправильний після переходу в інтегральний тип


Це важливо, коли ви збираєтесь -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 біти, а якщо ints у вашій реалізації є доповненням двох, то допустимий діапазон перерахунок становить від -8 до 7. Якщо базовий тип не підписаний, то він має 3 біти, а діапазон від 0 до 7. Перевірте свою документацію на компілятор, якщо вам це важливо (наприклад, якщо ви хочете передати цілісні значення, відмінні від нумераторів, до перелічений тип, тоді вам потрібно знати, чи знаходиться значення в діапазоні перерахування, чи ні - якщо не отримане значення перерахунку не визначене).

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


0

Перевірте це за допомогою std::is_signed<std::underlying_type+ скопійованих перерахунків за замовчуванням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.