Як автоматично перетворити сильно набраний enum в int?


165
#include <iostream>

struct a {
  enum LOCAL_A { A1, A2 };
};
enum class b { B1, B2 };

int foo(int input) { return input; }

int main(void) {
  std::cout << foo(a::A1) << std::endl;
  std::cout << foo(static_cast<int>(b::B2)) << std::endl;
}

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

Отже, чи є спосіб перетворити сильно набране значення перерахунку в цілий тип без введення? Якщо так, то як?

Відповіді:


134

Сильно набрані переліки, спрямовані на вирішення декількох проблем, а не лише на предмет масштабування, як ви згадували у своєму запитанні:

  1. Забезпечте безпеку типу, тим самим усуваючи неявне перетворення на ціле число шляхом інтегральної промоції.
  2. Вкажіть основні типи.
  3. Забезпечте чітке охоплення.

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

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


2
Це ще один дивний приклад „ми краще знаємо, що ти хочеш зробити” від творців C ++. Звичайні (старі) перерахунки мали низку переваг, як неявна конверсія в індекси, безперебійне використання бітових операцій тощо. Новий стиль перерахунків додав дійсно чудовий показник, але ... Ви не можете використовувати лише цю річ (навіть із явними основна специфікація типу!). Тож тепер ви або змушені використовувати переліки у старому стилі з такими хитрощами, як їх розміщення в структурі, або створювати неприємні обхідні шляхи для нових переліків, як-от створити власну обгортку навколо std :: vector, щоб подолати цю річ. немає коментарів
avtomaton

152

Як вже говорили інші, ви не можете мати неявну конверсію, і це побічна конструкція.

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

template <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
    return static_cast<typename std::underlying_type<E>::type>(e);
}

std::cout << foo(to_underlying(b::B2)) << std::endl;

75

Версія відповіді Р. Мартіньо Фернандеса на C ++ 14 була б такою:

#include <type_traits>

template <typename E>
constexpr auto to_underlying(E e) noexcept
{
    return static_cast<std::underlying_type_t<E>>(e);
}

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


Оновлення
Це також з'являється в " Ефективний сучасний C ++" Скотта Майерса . Дивіться пункт 10 (він детально описаний на заключних сторінках пункту в моїй копії книги).


18
#include <cstdlib>
#include <cstdio>
#include <cstdint>

#include <type_traits>

namespace utils
{

namespace details
{

template< typename E >
using enable_enum_t = typename std::enable_if< std::is_enum<E>::value, 
                                               typename std::underlying_type<E>::type 
                                             >::type;

}   // namespace details


template< typename E >
constexpr inline details::enable_enum_t<E> underlying_value( E e )noexcept
{
    return static_cast< typename std::underlying_type<E>::type >( e );
}   


template< typename E , typename T>
constexpr inline typename std::enable_if< std::is_enum<E>::value &&
                                          std::is_integral<T>::value, E
                                         >::type 
 to_enum( T value ) noexcept 
 {
     return static_cast<E>( value );
 }

} // namespace utils




int main()
{
    enum class E{ a = 1, b = 3, c = 5 };

    constexpr auto a = utils::underlying_value(E::a);
    constexpr E    b = utils::to_enum<E>(5);
    constexpr auto bv = utils::underlying_value(b);

    printf("a = %d, b = %d", a,bv);
    return 0;
}

3
Це не зменшує введення тексту та не робить більш чистим код, а також має побічні ефекти, що ускладнюють пошук таких неявних перетворень у великих проектах. Static_cast було б простіше шукати проект широко, ніж ці конструкції.
Атул Кумар

3
@AtulKumar Як шукати static_cast простіше, ніж шукати to_enum?
Йоганн Герелл

1
Ця відповідь потребує певного пояснення та документації.
Гонки легкості на орбіті

17

Ні, природного шляху немає .

Насправді, одним із мотивів сильно набраного enum classC ++ 11 є запобігання їх мовчазному перетворенню на int.


Подивіться на відповідь Хуршида Нормурадова. Він надходить «природним шляхом» і так, як задумано у «Мові програмування на C ++ (4-е видання)». Це не є автоматичним способом, і це добре.
PapaAtHome

@PapaAtHome Я не розумію переваги цього над static_cast. Не сильно змінилося в чистоті набору тексту чи коду. Який природний шлях тут? Функція, що повертає значення?
Атул Кумар

1
@ user2876962 Перевага для мене полягає в тому, що вона не є автоматичною або "мовчазною", як стверджує Iammilind. Це запобігає виникненню помилок у пошуку різноманітних помилок. Ви все ще можете зробити акторський склад, але ви змушені думати про це. Таким чином ви знаєте, що робите. Для мене це частина звички "безпечного кодування". Я вважаю за краще, щоб жодна конверсія не робилася автоматично, є можливість, що це може призвести до помилки. До цієї категорії, якщо ви запитаєте мене, є кілька змін C ++ 11, пов'язаних із системою типів.
PapaAtHome

17

Причина відсутності неявного перетворення (за задумом) була наведена в інших відповідях.

Я особисто використовую unry operator+для перетворення класів enum в їх базовий тип:

template <typename T>
constexpr auto operator+(T e) noexcept
    -> std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>>
{
    return static_cast<std::underlying_type_t<T>>(e);
}

Що дає зовсім небагато "набору тексту":

std::cout << foo(+b::B2) << std::endl;

Де я фактично використовую макрос для створення переліків і функцій оператора в одному кадрі.

#define UNSIGNED_ENUM_CLASS(name, ...) enum class name : unsigned { __VA_ARGS__ };\
inline constexpr unsigned operator+ (name const val) { return static_cast<unsigned>(val); }

13

Сподіваюся, що це допоможе вам або комусь іншому

enum class EnumClass : int //set size for enum
{
    Zero, One, Two, Three, Four
};

union Union //This will allow us to convert
{
    EnumClass ec;
    int i;
};

int main()
{
using namespace std;

//convert from strongly typed enum to int

Union un2;
un2.ec = EnumClass::Three;

cout << "un2.i = " << un2.i << endl;

//convert from int to strongly typed enum
Union un;
un.i = 0; 

if(un.ec == EnumClass::Zero) cout << "True" << endl;

return 0;
}

33
Це називається "тип покарання", і хоча його підтримують деякі компілятори, це не портативно, оскільки стандарт C ++ говорить, що після встановлення un.iцього "активного члена" ви можете читати лише активного члена об'єднання.
Джонатан Вейклі

6
@JonathanWakely Ви технічно правильні, але я ніколи не бачив компілятора, де це не працює надійно. Такі речі, як анонімні профспілки, так і #прагма колись є стандартними стандартами.
BigSandwich

5
Навіщо використовувати щось, що стандарт явно забороняє, коли буде виконано простий склад? Це просто неправильно.
Пол Грок

1
Технічно правильно чи ні, для мене це набагато легше читати, ніж інші рішення, знайдені тут. І що для мене важливіше, він може бути використаний для вирішення не тільки серіалізації, а й десеріалізації класу enum з легкістю та зручним для читання форматом.
Marcin Waśniowski

6
Я абсолютно впадаю у відчай, що є люди, які вважають цю безладну не визначену поведінку "способом більш зрозумілим", ніж простим static_cast.
підкреслюй_d

13

Коротка відповідь - ви не можете, як зазначено вище в публікаціях. Але в моєму випадку я просто не хотів захаращувати простір імен, але все одно мав неявні перетворення, тому я просто зробив:

#include <iostream>

using namespace std;

namespace Foo {
   enum Foo { bar, baz };
}

int main() {
   cout << Foo::bar << endl; // 0
   cout << Foo::baz << endl; // 1
   return 0;
}

Різновиди простору імен додає шар безпеки типу, поки мені не доведеться статично ставити будь-які значення перерахунків до базового типу.


3
Він не додає ніякої безпеки типу (дійсно, ви просто зняли безпеку типу) - це лише додає масштабування імен.
Гонки легкості на орбіті

@LightnessRacesinOrbit так, я згоден. Я збрехав. Технічно, тип точно під простором / областю імен і повністю відповідає умовам Foo::Foo. До членів можна отримати доступ як Foo::barі, так Foo::bazі непрямо (але це не дуже безпека типу). Мабуть, краще майже завжди використовувати курси переліку, особливо якщо починається новий проект.
сонцестояння333

6

Це здається неможливим , з рідним enum class, але , ймовірно , ви можете знущатися enum classз class:

В цьому випадку,

enum class b
{
    B1,
    B2
};

було б рівнозначно:

class b {
 private:
  int underlying;
 public:
  static constexpr int B1 = 0;
  static constexpr int B2 = 1;
  b(int v) : underlying(v) {}
  operator int() {
      return underlying;
  }
};

Це здебільшого еквівалентно оригіналу enum class. Ви можете безпосередньо повернутися b::B1до функції із типом повернення b. Ви можете зробити switch caseз цим і т.д.

І в дусі цього прикладу ви можете використовувати шаблони (можливо, разом з іншими речами) для узагальнення та знущань над будь-яким можливим об’єктом, визначеним enum classсинтаксисом.


але B1 і B2 повинні бути визначені поза класом ... або це непридатно для case - header.h <- клас b - main.cpp <---- myvector.push_back (B1)
Fl0

Хіба це не повинно бути "статичним constexpr b" замість "static constexpr int"? Інакше b :: B1 - це лише інта, яка взагалі не має безпеки.
Some Guy

4

Як багато хто говорив, немає способу автоматичного перетворення без додавання накладних витрат та надмірної складності, але ви можете трохи зменшити введення тексту та покращити його, використовуючи лямбда, якщо в сценарії буде використано трохи актерів. Це додасть додаткових функцій накладного виклику, але зробить код більш читабельним порівняно з довгими рядками static_cast, як видно нижче. Це може бути не корисно для проекту, а лише для класу.

#include <bitset>
#include <vector>

enum class Flags { ......, Total };
std::bitset<static_cast<unsigned int>(Total)> MaskVar;
std::vector<Flags> NewFlags;

-----------
auto scui = [](Flags a){return static_cast<unsigned int>(a); };

for (auto const& it : NewFlags)
{
    switch (it)
    {
    case Flags::Horizontal:
        MaskVar.set(scui(Flags::Horizontal));
        MaskVar.reset(scui(Flags::Vertical)); break;
    case Flags::Vertical:
        MaskVar.set(scui(Flags::Vertical));
        MaskVar.reset(scui(Flags::Horizontal)); break;

   case Flags::LongText:
        MaskVar.set(scui(Flags::LongText));
        MaskVar.reset(scui(Flags::ShorTText)); break;
    case Flags::ShorTText:
        MaskVar.set(scui(Flags::ShorTText));
        MaskVar.reset(scui(Flags::LongText)); break;

    case Flags::ShowHeading:
        MaskVar.set(scui(Flags::ShowHeading));
        MaskVar.reset(scui(Flags::NoShowHeading)); break;
    case Flags::NoShowHeading:
        MaskVar.set(scui(Flags::NoShowHeading));
        MaskVar.reset(scui(Flags::ShowHeading)); break;

    default:
        break;
    }
}

2

Комітет C ++ зробив один крок вперед (розширення перерахунків із глобального простору імен) і п’ятдесят кроків назад (жодного переліку типу перерахування на ціле число немає). На жаль, enum classце просто не використовується, якщо вам потрібне значення перерахунку будь-яким несимволічним способом.

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

struct TextureUploadFormat {
    enum Type : uint32 {
        r,
        rg,
        rgb,
        rgba,
        __count
    };
};

// must use ::Type, which is the extra typing with this method; beats all the static_cast<>()
uint32 getFormatStride(TextureUploadFormat::Type format){
    const uint32 formatStride[TextureUploadFormat::__count] = {
        1,
        2,
        3,
        4
    };
    return formatStride[format]; // decays without complaint
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.