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

How to automatically convert strongly typed enum into int?

本文关键字:转换 整型 枚举 强类型 何自动      更新时间:2023-10-16
#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是强类型枚举试图实现的,但有一个小小的区别:普通枚举可以转换为整数类型,而强类型枚举如果没有强制类型转换就不能转换为整数类型。

那么,有没有一种方法可以将强类型枚举值转换为不需要强制类型转换的整数类型呢?如果是,怎么做?

正如其他人所说,你不能有隐式转换,这是设计的。

如果需要,可以避免在强制转换中指定基础类型。

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;

强类型枚举旨在解决多个问题,而不仅仅是您在问题中提到的范围问题:

  1. 提供类型安全,从而通过整型提升消除隐式的整数转换。
  2. 指定底层类型。
  3. 提供强作用域
因此,不可能隐式地将强类型枚举转换为整数,甚至不可能将其底层类型转换为整数——这就是其思想。因此,您必须使用static_cast来显式转换。

如果您唯一的问题是作用域,并且您真的希望隐式提升为整数,那么您最好使用非强类型enum,其声明所在结构的作用域。

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

与前面的答案一样,这适用于任何类型的枚举和底层类型。我添加了noexcept关键字,因为它永远不会抛出异常。


更新
这也出现在Scott Meyers的Effective Modern c++中。见第10项(详见我那本书的最后几页)。


c++ 23版本将使用std::to_underlying函数:
#include <utility>
std::cout << std::to_underlying(b::B2) << std::endl;

…或者如果底层类型可以是1字节类型:

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

没有隐式转换(通过设计)的原因在其他答案中给出。

我个人使用一元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 Foo { bar, baz };
}
int main() {
   cout << Foo::bar << endl; // 0
   cout << Foo::baz << endl; // 1
   return 0;
}

命名空间增加了一层类型安全,而我不必将任何枚举值静态强制转换为底层类型。

没有自然的方法。

事实上,在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;
}

希望这对您或其他人有所帮助

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来说似乎是不可能的,但是也许您可以用class来模拟enum 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的函数中直接返回b::B1。你可以用它做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
}

正如许多人所说,没有办法在不增加开销和太多复杂性的情况下自动转换,但是如果在某个场景中经常使用某些强制转换,则可以通过使用lambdas减少输入并使其看起来更好。这将增加一点函数开销调用,但与长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;
    }
}

总结

问题:

是否有一种方法可以将强类型枚举值转换为整数类型而不需要强制类型转换?如果是,怎么做?

答:

不,没有。如果没有显式强制转换,强类型枚举不能转换为整数。然而,弱枚举可以,因为它们将自动隐式强制转换。因此,如果您希望自动隐式转换为int类型,请考虑使用c风格的弱枚举(参见"Going further"中的更多信息)。章节)。

从这里(强调添加):https://en.cppreference.com/w/cpp/language/enum——>

static_cast可以用来获得枚举数的数值。

进一步:讨论c++ 中 (C-style)和 (c++ enum class)枚举类型

在c++中有两种类型的枚举:

  1. "unscoped", "regular", "weak", "弱类型"或"C-style"枚举,
  2. "scoped", "strong", "strong type ", "enum class",或" c++ -style"枚举。

"Scoped"枚举,或"strong"枚举,给出两个额外的"特性"。超出了什么"常规"枚举给你。

范围的枚举:

  1. 不允许从枚举类型隐式转换为整数类型(所以不能做你想做的事隐式!),和
  2. 他们"scope"

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 

第一个"特征"可能实际上是你不想要的,在这种情况下,你只需要使用常规的c风格enum来代替!好处是:你仍然可以'作用域'或";namespace"枚举,就像几十年来在C语言中所做的那样,只需在其名称前加上枚举类型名称,如:

2。普通enum的例子(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;

注意,您仍然可以获得"作用域"的好处。只需添加MY_ENUM_ "作用域"到每个枚举的前面!

3。普通枚举和枚举类一起使用:

在这里测试代码:https://onlinegdb.com/BkWGqlqz_.

main.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 Worldn");
    // 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。如何遍历枚举:

  1. *****[我的答案]如何迭代的完整示例1。弱类型C-style作用域,强类型c++ enum class 枚举:如何迭代枚举?
  2. 在c++中迭代枚举类的常用方法是什么?

R. Martinho Fernandes和class Skeleton的答案的扩展:他们的答案展示了如何使用typename std::underlying_type<EnumType>::typestd::underlying_type_t<EnumType>将具有static_cast的枚举值转换为底层类型的值。与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的值将不会作为数字打印,而是作为字符打印,这很可能不是您想要的。因此,有时您宁愿将枚举值转换为"基础类型"之类的东西,但在必要时使用整数提升。

如果有必要,可以对强制转换的结果应用一元operator+来强制整型提升。但是,您也可以使用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);

最好将此表达式包装在某个helper模板函数中:

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