简体   繁体   English

如何自动将强类型枚举转换为 int?

[英]How to automatically convert strongly typed enum into 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;
}

The a::LOCAL_A is what the strongly typed enum is trying to achieve, but there is a small difference : normal enums can be converted into integer type, while strongly typed enums can not do it without a cast. a::LOCAL_A是强类型枚举试图实现的,但有一个小的区别:普通枚举可以转换为整数类型,而强类型枚举在没有强制转换的情况下无法做到。

So, is there a way to convert a strongly typed enum value into an integer type without a cast?那么,有没有一种方法可以将强类型枚举值转换为整数类型而无需强制转换? If yes, how?如果是,如何?

As others have said, you can't have an implicit conversion, and that's by-design.正如其他人所说,您不能进行隐式转换,这是设计使然。

If you want you can avoid the need to specify the underlying type in the cast.如果您愿意,您可以避免在强制转换中指定基础类型。

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;

Strongly typed enums aiming to solve multiple problems and not only scoping problem as you mentioned in your question:强类型枚举旨在解决多个问题,而不仅仅是您在问题中提到的范围问题:

  1. Provide type safety, thus eliminating implicit conversion to integer by integral promotion.提供类型安全,从而通过整数提升消除到整数的隐式转换。
  2. Specify underlying types.指定基础类型。
  3. Provide strong scoping.提供强有力的范围界定。

Thus, it is impossible to implicitly convert a strongly typed enum to integers, or even its underlying type - that's the idea.因此,不可能将强类型枚举隐式转换为整数,甚至是其底层类型——这就是想法。 So you have to use static_cast to make conversion explicit.所以你必须使用static_cast来明确转换。

If your only problem is scoping and you really want to have implicit promotion to integers, then you better off using not strongly typed enum with the scope of the structure it is declared in.如果您唯一的问题是作用域,并且您确实希望对整数进行隐式提升,那么您最好使用具有声明它的结构范围的非强类型枚举。

A C++14 version of the answer provided by R. Martinho Fernandes would be: R. Martinho Fernandes提供的答案的 C++14 版本将是:

#include <type_traits>

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

As with the previous answer, this will work with any kind of enum and underlying type.与前面的答案一样,这将适用于任何类型的枚举和基础类型。 I have added the noexcept keyword as it will never throw an exception.我添加了noexcept关键字,因为它永远不会抛出异常。


Update更新
This also appears in Effective Modern C++ by Scott Meyers .这也出现在Scott Meyers 的 Effective Modern C++ 中 See item 10 (it is detailed in the final pages of the item within my copy of the book).请参阅第 10 项(在我的书副本中该项目的最后几页中有详细说明)。


A C++23 version would be to use the std::to_underlying function: C++23 版本将使用std::to_underlying函数:

#include <utility>

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

...or if the underlying type could be a 1 byte type : ...或者如果基础类型可能是1 字节类型

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

The reason for the absence of implicit conversion (by design) was given in other answers.在其他答案中给出了没有隐式转换(通过设计)的原因。

I personally use unary operator+ for the conversion from enum classes to their underlying type:我个人使用一元运算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);
}

Which gives quite little "typing overhead":这几乎没有“打字开销”:

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

Where I actually use a macro to create enums and the operator functions in one shot.我实际上使用宏来一次性创建枚举和运算符函数。

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

Short answer is you can't as above posts point out.简短的回答是你不能像上面的帖子指出的那样。 But for my case, I simply didn't want to clutter the namespace but still have implicit conversions, so I just did:但就我而言,我只是不想弄乱命名空间但仍然有隐式转换,所以我只是这样做了:

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

The namespacing sort of adds a layer of type-safety while I don't have to static cast any enum values to the underlying type.命名空间增加了一层类型安全,而我不必将任何枚举值静态转换为基础类型。

No. There is no natural way .不,没有自然的方法

In fact, one of the motivations behind having strongly typed enum class in C++11 is to prevent their silent conversion to int .事实上,在 C++11 中使用强类型enum class的动机之一是防止它们静默转换为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;
}

Hope this helps you or someone else希望这对您或其他人有所帮助

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

This seems impossible with the native enum class , but probably you can mock a enum class with a class :这对于原生的enum class来说似乎是不可能的,但也许你可以用一个class来模拟一个enum class类:

In this case,在这种情况下,

enum class b
{
    B1,
    B2
};

would be equivalent to:相当于:

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

This is mostly equivalent to the original enum class .这主要相当于原始的enum class You can directly return b::B1 for in a function with return type b .您可以在返回类型为b的函数中直接返回b::B1 for 。 You can do switch case with it, etc.你可以用它做switch case ,等等。

And in the spirit of this example you can use templates (possibly together with other things) to generalize and mock any possible object defined by the enum class syntax.并且本着本示例的精神,您可以使用模板(可能与其他东西一起)来概括和模拟enum class语法定义的任何可能的对象。

As many said, there is no way to automatically convert without adding overheads and too much complexity, but you can reduce your typing a bit and make it look better by using lambdas if some cast will be used a bit much in a scenario.正如许多人所说,没有办法在不增加开销和太多复杂性的情况下自动转换,但是如果在某个场景中会使用一些强制转换,您可以使用 lambdas 减少一点输入并使其看起来更好。 That would add a bit of function overhead call, but will make code more readable compared to long static_cast strings as can be seen below.这会增加一些函数开销调用,但与长 static_cast 字符串相比,会使代码更具可读性,如下所示。 This may not be useful project wide, but only class wide.这可能在项目范围内没有用,而在类范围内是有用的。

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

The C++ committee took one step forward (scoping enums out of global namespace) and fifty steps back (no enum type decay to integer). C++ 委员会向前迈了一步(将枚举范围限定在全局命名空间之外),后退了 50 步(没有枚举类型衰减为整数)。 Sadly, enum class is simply not usable if you need the value of the enum in any non-symbolic way.遗憾的是,如果您需要以任何非符号方式获取枚举的值,那么enum class根本不可用。

The best solution is to not use it at all, and instead scope the enum yourself using a namespace or a struct.最好的解决方案是根本不使用它,而是使用命名空间或结构自己限定枚举。 For this purpose, they are interchangable.为此,它们是可互换的。 You will need to type a little extra when refering to the enum type itself, but that will likely not be often.在引用枚举类型本身时,您需要输入一些额外的内容,但这可能不会经常发生。

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
}

Summary概括

Question:问题:

is there a way to convert a strongly typed enum value into an integer type without a cast?有没有办法将强类型枚举值转换为整数类型而无需强制转换? If yes, how?如果是,如何?

Answer:回答:

No, there is not.不,那里没有。 Strongly typed enums can NOT be converted to integers without an explicit cast.强类型枚举不能在没有显式转换的情况下转换为整数。 Weak enums can, however, as they will be automatically implicitly cast.然而,弱枚举可以,因为它们会被自动隐式转换。 So, if you'd like automatic implicit conversion to an int, consider using a C-style weak enum instead (see more on this in the "Going further" section below).因此,如果您想自动隐式转换为 int,请考虑改用 C 风格的弱枚举(请参阅下面的“进一步介绍”部分中的更多内容)。

From here (emphasis added): https://en.cppreference.com/w/cpp/language/enum --> under the section "Scoped enumerations":从这里(强调添加): https://en.cppreference.com/w/cpp/language/enum --> 在“Scoped enumerations”部分下:

There are no implicit conversions from the values of a scoped enumerator [AKA: "strong enum"] to integral types, although static_cast may be used to obtain the numeric value of the enumerator.没有从作用域枚举器 [AKA: "strong enum"] 的值到整数类型的隐式转换,尽管可以使用static_cast来获取枚举器的数值。

Going further: discussion of weak (C-style) vs strong (C++ enum class ) enum types in C++更进一步:讨论 C++ 中的(C 风格)与(C++ enum class )枚举类型

In C++ there are two types of enums:在 C++ 中有两种类型的枚举:

  1. "unscoped", "regular", "weak", "weakly typed", or "C-style" enums, and “unscoped”、“regular”、“weak”、“weakly typed”或“C-style”枚举,以及
  2. "scoped", "strong", "strongly typed", "enum class", or "C++-style" enums. “作用域”、“强”、“强类型”、“枚举类”或“C++ 风格”枚举。

"Scoped" enums, or "strong" enums, give two additional "features" beyond what "regular" enums give you. “作用域”枚举或“强”枚举提供了超出“常规”枚举所提供的两个额外“功能”。

Scoped enums:范围枚举:

  1. don't allow implicit casting from the enum type to an integer type (so you can't do what you want to do implicitly !), and不允许从枚举类型隐式转换为整数类型(所以你不能隐式地做你想做的事!),和
  2. they "scope" the enum so that you have to access the enum through its enum type name.他们“限定”枚举,以便您必须通过枚举类型名称访问枚举。

1. Example of an enum class (available only in C++): 1. 枚举类示例(仅在 C++ 中可用):

// 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 explicit casting works just fine!:
int i1 = static_cast<int>(e); // explicit C++-style cast 
int i2 = (int)e;              // explicit C-style cast 

The first "feature" may actually be something you don't want, in which case you just need to use a regular C-style enum instead!第一个“功能”实际上可能是您想要的东西,在这种情况下,您只需要使用常规的 C 样式枚举来代替! And the nice thing is: you can still "scope" or "namespace" the enum, as has been done in C for decades, by simply prepending its name with the enum type name, like this:好消息是:您仍然可以像在 C 中所做的那样,通过简单地在枚举类型名称前面加上枚举类型名称,来为枚举“范围”或“命名空间”,如下所示:

2. Example of a regular enum (available in both C and C++): 2. 常规枚举示例(在 C 和 C++ 中均可用):

// regular enum (AKA: "weak" or "C-style" enum)
enum my_enum
{
    // C-style-scoped through the `MY_ENUM_` prefix
    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;

Notice you still get the benefit of "scoping" simply by adding the MY_ENUM_ "scope" to the front of each enum!请注意,只需将MY_ENUM_ “范围”添加到每个枚举的前面,您仍然可以获得“范围”的好处!

3. Both regular enums and enum classes together: 3. 常规枚举和枚举类一起使用:

Test the code here: https://onlinegdb.com/BkWGqlqz_ .在此处测试代码: https ://onlinegdb.com/BkWGqlqz_。

main.cpp :主.cpp

#include <iostream>
#include <stdio.h>

// enum class (AKA: "strong" or "scoped" enum [available only in C++, not C])
enum class my_enum
{
    A = 0,
    B,
    C,
};

// regular enum (AKA: "weak" or "C-style" enum [available in BOTH C and C++])
enum my_enum2
{
    MY_ENUM_A = 0,
    MY_ENUM_B,
    MY_ENUM_C,
};


int main()
{
    printf("Hello World\n");

    // 1) scoped enum

    my_enum e = my_enum::A; // scoped through `my_enum::`
    e = my_enum::B;
    
    // IMPLICIT CASTING TO INT IS NOT ALLOWED!
    // int i = e; // "error: cannot convert ‘my_enum’ to ‘int’ in initialization"
    // But this explicit C++-style cast works fine:
    int i1 = static_cast<int>(e); 
    // This explicit C-style cast works fine too, and is easier to read
    int i2 = (int)e;
    
    
    // 2) regular enum 
    
    my_enum2 e2 = MY_ENUM_A; // scoped through `MY_ENUM_`
    e2 = MY_ENUM_B;
    
    // This implicit cast works fine / IS allowed on C-style enums!
    int i3 = e2;
    // These explicit casts are also fine, but explicit casting is NOT 
    // required for regular enums.
    int i4 = static_cast<int>(e2); // explicit C++-style cast 
    int i5 = (int)e2;              // explicit C-style cast 

    return 0;
}

4. How to iterate over enums: 4.如何迭代枚举:

  1. *****[my answer] Full examples of how to iterate over both 1. weakly-typed C-style and 2. scoped, strongly-typed C++ enum class enums: How can I iterate over an enum? *****[我的回答] 如何迭代 1.弱类型 C 风格和 2.作用域、强类型 C++ enum class枚举的完整示例:如何迭代枚举?
  2. [my Q&A] What are commonly-used ways to iterate over an enum class in C++? [我的问答]在 C++ 中迭代枚举类的常用方法是什么?

An extension to the answers from R. Martinho Fernandes and Class Skeleton : Their answers show how to use typename std::underlying_type<EnumType>::type or std::underlying_type_t<EnumType> to convert your enumeration value with a static_cast to a value of the underlying type. R. Martinho FernandesClass Skeleton的答案的扩展:他们的答案显示了如何使用typename std::underlying_type<EnumType>::typestd::underlying_type_t<EnumType>将您的枚举值与static_cast转换为一个值的基础类型。 Compared to a static_cast to some specific integer type, like, static_cast<int> this has the benefit of being maintenance friendly, because when the underlying type changes, the code using std::underlying_type_t will automatically use the new type.与将static_cast转换为某些特定整数类型(如static_cast<int> )相比,这具有易于维护的优点,因为当底层类型更改时,使用std::underlying_type_t的代码将自动使用新类型。

This, however, is sometimes not what you want: Assume you wanted to print out enumeration values directly, for example to std::cout , like in the following example:但是,这有时不是您想要的:假设您想直接打印出枚举值,例如打印到std::cout ,如下例所示:

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

If you later change the underlying type to a character type, like, uint8_t , then the value of EnumType::Green will not be printed as a number, but as a character, which is most probably not what you want.如果您稍后将基础类型更改为字符类型,例如uint8_t ,则EnumType::Green的值将不会打印为数字,而是作为字符,这很可能不是您想要的。 Thus, you sometimes would rather convert the enumeration value into something like "underlying type, but with integer promotion where necessary".因此,您有时宁愿将枚举值转换为“基础类型,但在必要时进行整数提升”之类的东西。

It would be possible to apply the unary operator+ to the result of the cast to force integer promotion if necessary.如有必要,可以将一元运算operator+应用于强制转换的结果以强制进行整数提升。 However, you can also use std::common_type_t (also from header file <type_traits> ) to do the following:但是,您也可以使用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);

Preferrably you would wrap this expression in some helper template function:最好将这个表达式包装在一些帮助模板函数中:

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

Which would then be more friendly to the eyes, be maintenance friendly with respect to changes to the underlying type, and without need for tricks with operator+ :这样对眼睛更友好,对底层类型的更改维护友好,并且不需要operator+的技巧:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM