Как автоматически конвертировать строго типизированный 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 ответов
Строго типизированные перечисления, нацеленные на решение множества проблем, а не только задачи определения объема, как вы упомянули в своем вопросе:
- Обеспечьте безопасность типов, таким образом устраняя неявное преобразование в целое число путем интегрального продвижения.
- Укажите базовые типы.
- Обеспечить сильное определение объема.
Таким образом, невозможно неявно преобразовать строго типизированное перечисление в целые числа или даже в его базовый тип - вот в чем идея. Так что вы должны использовать 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++ есть два типа перечислений:
- "обычные", "слабые", "слабо типизированные" или "C-style" перечисления и
- "scoped", "strong", "строго типизированный", "enum class" или "C++-style" перечисления.
Перечисления с ограниченной областью действия, или "сильные" перечисления, предоставляют две дополнительные "возможности" помимо того, что предоставляют вам "обычные" перечисления. Перечисления с областью действия:
- не разрешать неявное приведение типа перечисления к целочисленному типу (чтобы вы не могли делать то, что хотите, неявно!), и
- они "охватывают" перечисление, так что вы должны получить доступ к перечислению через его имя типа перечисления.
Пример класса перечисления:
// 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».