221
#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就是强类型枚举试图实现的目标,但有一个小的区别:普通枚举可以转换为整数类型,而强类型枚举在没有强制转换的情况下无法做到这一点。

那么,有没有一种方法可以将强类型枚举值转换为整数类型而无需强制转换?如果是,如何?

4

13 回答 13

179

正如其他人所说,您不能进行隐式转换,这是设计使然。

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

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;
于 2011-12-02T14:00:23.687 回答
159

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

  1. 提供类型安全,从而通过整数提升消除到整数的隐式转换。
  2. 指定基础类型。
  3. 提供强有力的范围界定。

因此,不可能将强类型枚举隐式转换为整数,甚至是其底层类型——这就是想法。所以你必须使用static_cast明确的转换。

如果您唯一的问题是作用域,并且您确实希望对整数进行隐式提升,那么您最好使用具有声明它的结构范围的非强类型枚举。

于 2011-12-02T13:53:18.583 回答
104

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;
于 2015-10-12T14:06:17.550 回答
30

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

我个人使用一元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); }
于 2017-02-13T07:35:25.357 回答
26

简短的回答是你不能像上面的帖子指出的那样。但就我而言,我只是不想弄乱命名空间但仍然有隐式转换,所以我只是这样做了:

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

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

于 2017-09-19T07:50:59.873 回答
23

不,没有自然的方法

事实上,enum class在 C++11 中进行强类型化背后的动机之一是防止它们静默转换为int.

于 2011-12-02T13:46:24.947 回答
20
#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;
}
于 2013-09-07T10:07:12.267 回答
11

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

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;
}
于 2013-10-10T03:54:35.087 回答
8

这对于 native 来说似乎是不可能的,但也许你可以用 aenum class模拟a :enum classclass

在这种情况下,

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您可以在具有返回类型的函数中直接返回for b。你可以switch case用它,等等。

本着这个例子的精神,您可以使用模板(可能与其他东西一起)来概括和模拟由enum class语法定义的任何可能的对象。

于 2017-06-06T20:51:11.233 回答
5

正如许多人所说,没有办法在不增加开销和太多复杂性的情况下自动转换,但是如果在某个场景中会使用一些强制转换,您可以使用 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;
    }
}
于 2015-03-21T23:11:49.570 回答
4

C++ 委员会向前迈了一步(将枚举范围限定在全局命名空间之外),后退了 50 步(没有枚举类型衰减为整数)。可悲的是,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
}
于 2020-07-10T16:15:23.393 回答
3

概括

从这里(强调添加):https://en.cppreference.com/w/cpp/language/enum --> 在“Scoped enumerations”部分下:

没有从作用域枚举器[AKA: "strong enum"] 的值到整数类型的隐式转换,尽管static_cast可以用于获取枚举器的数值。

走得更远

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

  1. “常规”、“弱”、“弱类型”或“C 风格”枚举,以及
  2. “作用域”、“强”、“强类型”、“枚举类”或“C++ 风格”枚举。

“作用域”枚举或“强”枚举提供了超出“常规”枚举所提供的两个额外“功能”。范围枚举:

  1. 不允许从枚举类型隐式转换为整数类型(所以你不能隐式地做你想做的事!),和
  2. 他们“限定”枚举,以便您必须通过枚举类型名称访问枚举。

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 this works fine:
int i = static_cast<int>(e);

第一个“功能”实际上可能是您想要的东西,在这种情况下,您只需要使用常规的 C 样式枚举来代替!好消息是:您仍然可以像在 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;

MY_ENUM_请注意,只需将“范围”添加到每个枚举的前面,您仍然可以获得“范围”的好处!

3. 常规枚举和枚举类一起使用:

在此处测试代码:https ://onlinegdb.com/BkWGqlqz_ 。

主.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.如何迭代枚举:

  1. *****[我的回答] 如何迭代 1.弱类型 C 风格和 2.作用域、强类型 C++enum class枚举的完整示例:如何迭代枚举?
  2. [我的问答]在 C++ 中迭代枚举类的常用方法是什么?
于 2020-11-26T01:19:55.240 回答
2

R. Martinho FernandesClass Skeleton的答案的扩展:他们的答案显示了如何使用typename std::underlying_type<EnumType>::typestd::underlying_type_t<EnumType>将您的枚举值与 a 转换static_cast为基础类型的值。与static_cast某些特定的整数类型相比,例如,static_cast<int>这具有易于维护的好处,因为当底层类型发生变化时,使用的代码std::underlying_type_t将自动使用新类型。

但是,这有时不是您想要的:假设您想直接打印出枚举值,例如 to 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);

最好将这个表达式包装在一些帮助模板函数中:

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);
于 2021-01-22T15:49:12.030 回答