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

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

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

14 ответов

Решение

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

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

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

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

Надеюсь, поможет!

Как уже говорили другие, у вас не может быть неявного преобразования, и это просто дизайн.

Если вы хотите, вы можете избежать необходимости указывать базовый тип в приведении.

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;

Версия ответа 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 (он подробно описан на последних страницах этого пункта в моей копии книги).

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

Я лично использую одинарный 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); }

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

#include <iostream>

using namespace std;

namespace Foo {
   enum { bar, baz };
}

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

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

Нет. Естественного пути нет.

На самом деле, одна из причин, побуждающих к строгому enum class в C++11, чтобы предотвратить их тихое преобразование в int,

#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;
}

Надеюсь, это поможет вам или кому-то еще

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;
}

Это кажется невозможным с родным 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 синтаксис.

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

Лучшее решение - не использовать его вообще, а вместо этого ограничить перечисление самостоятельно, используя пространство имен или структуру. Для этого они взаимозаменяемы. Вам нужно будет ввести немного больше при обращении к самому типу перечисления, но это, вероятно, будет нечасто.

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
}

Как уже говорили многие, нет способа автоматически конвертировать без добавления накладных расходов и чрезмерной сложности, но вы можете немного уменьшить объем печати и улучшить ее, используя лямбды, если какое-то приведение будет использоваться в сценарии немного. Это добавит немного служебных вызовов, но сделает код более читабельным по сравнению с длинными строками 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;
    }
}

TL; DR;

Неявное преобразование значений перечислителя с заданной областью [AKA: "strong enum"] в целочисленные типы не производится, хотя static_cast может использоваться для получения числового значения перечислителя.

(курсив мой)

Источник: https://en.cppreference.com/w/cpp/language/enum -> в разделе "Перечисления с ограничением".

Идти дальше

В C++ есть два типа перечислений:

  1. "обычные", "слабые", "слабо типизированные" или "C-style" перечисления и
  2. "scoped", "strong", "строго типизированный", "enum class" или "C++-style" перечисления.

Перечисления с ограниченной областью действия, или "сильные" перечисления, предоставляют две дополнительные "возможности" помимо того, что предоставляют вам "обычные" перечисления. Перечисления с областью действия:

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

Пример класса перечисления:

       // enum class (AKA: "strong" or "scoped" enum)
enum class my_enum
{
    A = 0,
    B,
    C,
};

my_enum e = my_enum::A; // scoped through `my_enum::`
e = my_enum::B;

// NOT ALLOWED!:
//   error: cannot convert ‘my_enum’ to ‘int’ in initialization
// int i = e; 

// But this works fine:
int i = static_cast<int>(e);

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

Пример обычного перечисления:

       // regular enum (AKA: "weak" or "C-style" enum)
enum my_enum
{
    MY_ENUM_A = 0,
    MY_ENUM_B,
    MY_ENUM_C,
};

my_enum e = MY_ENUM_A; // scoped through `MY_ENUM_`
e = MY_ENUM_B;

// This works fine!
int i = e;

Обратите внимание, что вы по-прежнему получаете преимущество "масштабирования", просто добавляя MY_ENUM_ "scope" перед каждым перечислением!


Проверьте приведенный выше код здесь: https://onlinegdb.com/SJQ7uthcP.

Дополнение к ответам Р. Мартиньо Фернандеса и Class Skeleton: их ответы показывают, как использовать typename std::underlying_type<EnumType>::type или std::underlying_type_t<EnumType>чтобы преобразовать значение перечисления с a в значение базового типа. По сравнению с static_cast к некоторому конкретному целочисленному типу, например, static_cast<int> Преимущество этого заключается в удобстве обслуживания, поскольку при изменении базового типа код, использующий std::underlying_type_t автоматически будет использовать новый тип.

Однако это иногда не то, что вам нужно: предположим, вы хотите напрямую распечатать значения перечисления, например, чтобы std::cout, как в следующем примере:

      enum class EnumType : int { Green, Blue, Yellow };
std::cout << static_cast<std::underlying_type_t<EnumType>>(EnumType::Green);

Если позже вы измените базовый тип на символьный, например, uint8_t, то значение EnumType::Greenбудет печататься не как число, а как символ, что, скорее всего, не то, что вам нужно. Таким образом, иногда вы предпочитаете преобразовывать значение перечисления во что-то вроде «базового типа, но с целочисленным повышением, где это необходимо».

При необходимости можно было бы применить унарный к результату приведения для принудительного целочисленного продвижения. Однако вы также можете использовать std::common_type_t (также из файла заголовка <type_traits>) сделать следующее:

      enum class EnumType : int { Green, Blue, Yellow };
std::cout << static_cast<std::common_type_t<int, std::underlying_type_t<EnumType>>>(EnumType::Green);

Желательно обернуть это выражение в некоторую вспомогательную функцию-шаблон:

      template <class E>
constexpr std::common_type_t<int, std::underlying_type_t<E>>
enumToInteger(E e) {
    return static_cast<std::common_type_t<int, std::underlying_type_t<E>>>(e);
}

Которая была бы более удобна для глаз, удобна для обслуживания в отношении изменений базового типа и не требует каких-либо уловок с operator+:

      std::cout << enumToInteger(EnumType::Green);

Я рекомендую сделать аналогичное (но, возможно, немного поддельное) поведение, используя объект, конструктор, принимающий перечисление, и оператор преобразования для преобразования в перечисление, например:

      // Defines the keys that may be used to recieve input
struct Key
{
    enum KeyEnum : unsigned int {
        A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, Numeric0, Numeric1, Numeric2, Numeric3, Numeric4, Numeric5, Numeric6, Numeric7, Numeric8, Numeric9,
        Space, LeftBracket, RightBracket, Semicolon, Apostrophe, Backslash, Slash, Comma, Dot, Backspace, LeftShift, RightShift, LeftCtrl, RightCtrl, LeftAlt, RightAlt, COUNT, UNKNOWN
    };

    Key(const KeyEnum key);

    operator KeyEnum() const;

private:
    KeyEnum key;
};

Где конструктор просто устанавливаетkeyи оператор преобразования возвращает его. Это позволяет мне создать ключ (целое число без знака, представляющее клавишу клавиатуры в этом сценарии) с помощью, например, «Key::A».

Другие вопросы по тегам