Как автоматически конвертировать строго типизированный enum в int?

166
#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то, чего пытается достичь строго типизированное перечисление, но есть небольшая разница: обычные перечисления могут быть преобразованы в целочисленный тип, в то время как строго типизированные перечисления не могут сделать это без приведения.

Итак, есть ли способ преобразовать строго типизированное значение перечисления в целочисленный тип без приведения? Если да, то как?

BЈовић
источник

Ответы:

134

Строго типизированные перечисления, нацеленные на решение множества проблем, а не только задачи определения объема, как вы упомянули в своем вопросе:

  1. Обеспечьте безопасность типов, таким образом устраняя неявное преобразование в целое число путем интегрального продвижения.
  2. Укажите базовые типы.
  3. Обеспечить сильное определение объема.

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

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

LF
источник
2
Это еще один странный пример «мы лучше знаем, что вы хотите сделать» от создателей C ++. Обычные (в старом стиле) перечисления имели массу преимуществ, таких как неявное преобразование в индексы, плавное использование побитовых операций и т. Д. Новые перечисления в стиле добавили действительно большую область видимости, но ... Вы не можете использовать только эту вещь (даже с явным базовая спецификация типа!). Так что теперь вы либо вынуждены использовать перечисления старого стиля с трюками, такими как помещение их в struct, либо создавать самые уродливые обходные пути для новых перечислений, например, создавать свою собственную обертку вокруг std :: vector, просто чтобы преодолеть эту вещь CAST. без комментариев
автомат
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;
Р. Мартиньо Фернандес
источник
76

Версия ответа 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ключевое слово, так как оно никогда не выдаст исключение.


Обновление
Это также появляется в Effective Modern 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 classв C ++ 11 состоит в том, чтобы предотвратить их тихое преобразование в int.

iammilind
источник
Посмотрите на ответ Хуршида Нормурадова. Он идет «естественным путем» и во многом соответствует «Языку программирования C ++ (4-е изд.)». Это не происходит автоматически, и это хорошо.
PapaAtHome
@PapaAtHome Я не понимаю преимущества этого по сравнению с static_cast. Не много изменений в наборе текста или чистоте кода. Какой здесь естественный путь? Функция, возвращающая значение?
Атул Кумар
1
@ user2876962 Для меня преимущество в том, что оно не является автоматическим или «тихим», как выразился Iammilind. Это мешает трудно найти ошибки. Вы все еще можете делать бросок, но вы вынуждены думать об этом. Таким образом, вы знаете, что делаете. Для меня это часть привычки «безопасного кодирования». Я предпочитаю, чтобы никакие преобразования не делались автоматически, если есть вероятность того, что это может привести к ошибке. Довольно много изменений в C ++ 11, связанных с системой типов, попадают в эту категорию, если вы спросите меня.
PapaAtHome
17

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

Я лично использую унарный operator+для преобразования из перечислимых классов в их базовый тип:

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); }
Pixelchemist
источник
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;
}
vis.15
источник
33
Это называется «типизацией» и, хотя поддерживается некоторыми компиляторами, не переносимо, так как стандарт C ++ говорит, что после установки un.iэто «активный член», и вы можете только читать активный член объединения.
Джонатан Уэйкли
6
@JonathanWakely Вы технически правы, но я никогда не видел компилятор, где это не работает надежно. Подобные вещи, анонимные союзы и #pragma когда-то являются стандартами де-факто.
BigSandwich
5
Зачем использовать то, что стандарт явно запрещает, когда подойдет простое приведение? Это просто неправильно.
Пол Грок
1
Технически правильно или нет, для меня это более читабельно, чем другие решения, найденные здесь. И что более важно для меня, это может быть использовано для решения не только сериализации, но также и десериализации класса enum с легкостью и читаемым форматом.
Марцин Waśniowski
6
Я абсолютно отчаиваюсь, что есть люди, которые считают это грязное неопределенное поведение «более читабельным», чем простым static_cast.
underscore_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;
}

Вид пространства имен добавляет слой безопасности типов, в то время как мне не нужно статически приводить какие-либо значения перечисления к базовому типу.

solstice333
источник
3
Он не добавляет никакой безопасности типов (на самом деле, вы только что удалили безопасность типов) - он только добавляет область видимости.
Гонки
@LightnessRacesinOrbit да, я согласен. Я солгал. Технически, если быть точным, тип находится только под пространством имен / областью видимости и полностью соответствует Foo::Foo. Члены могут быть доступны как Foo::barи, Foo::bazи могут быть неявно приведены (и поэтому не так много типов безопасности). Вероятно, лучше почти всегда использовать перечисляемые классы, особенно если начинаете новый проект.
солнцестояние
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 <- class b - main.cpp <---- myvector.push_back (B1)
Fl0
? Это не должно быть «статическим constexpr б» вместо «статического constexpr междунар" В противном случае, б :: B1 является лишь ИНТ, не типобезопасность вообще.
Какой - то парень
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 до целого числа). К сожалению, enum classпросто не может использоваться, если вам нужно значение enum любым не символическим способом.

Лучшее решение - вообще не использовать его, а вместо этого самим перечислить 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
}
Энн Куинн
источник