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

时间:2011-12-02 13:43:06

标签: c++ c++11 strongly-typed-enum

#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是强类型枚举试图实现的,但是有一点不同:普通枚举可以转换为整数类型,而强类型枚举不能在没有强制转换的情况下执行。

那么,有没有办法将强类型的枚举值转换为没有强制转换的整数类型?如果是,怎么样?

13 个答案:

答案 0 :(得分:130)

正如其他人所说的那样,你不能进行隐式转换,这是按设计进行的。

如果您愿意,可以避免在演员表中指定基础类型。

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 :(得分:109)

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

  1. 提供类型安全性,从而消除通过整数提升隐式转换为整数。
  2. 指定基础类型。
  3. 提供强有力的范围。
  4. 因此,不可能将强类型枚举隐式转换为整数,甚至是它的基础类型 - 这就是想法。因此,您必须使用static_cast来明确转换。

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

答案 2 :(得分:55)

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项(在我的书副本中的项目的最后几页详细说明。)

答案 3 :(得分:17)

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

答案 4 :(得分:15)

没有。 没有自然的方式

事实上,在C ++ 11中强烈键入enum class背后的动机之一是阻止他们将{s}转换为int

答案 5 :(得分:12)

希望这可以帮助你或其他人

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

答案 6 :(得分: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); }

答案 7 :(得分:9)

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

#include <iostream>

using namespace std;

namespace Foo {
   enum { bar, baz };
}

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

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

答案 8 :(得分:5)

使用原始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语法定义的任何可能的对象。

答案 9 :(得分:4)

正如许多人所说的那样,没有办法在不增加开销和太多复杂性的情况下自动转换,但是你可以通过使用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;
    }
}

答案 10 :(得分:3)

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
}

答案 11 :(得分:2)

对来自 R. Martinho FernandesClass 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);

最好将此表达式包装在一些辅助模板函数中:

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

答案 12 :(得分:0)

TLDR;

从{strong>范围枚举器 [AKA:“ strong enum”]的值到整数类型没有隐式转换,尽管static_cast可用于获取变量的数值。枚举器。

(添加了重点)

来源:https://en.cppreference.com/w/cpp/language/enum->在“范围枚举”部分下。

走得更远

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

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

“作用域”枚举或“强”枚举除了“常规”枚举提供的功能外,还提供了另外两个“功能”。范围枚举:

  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_“作用域”添加到每个枚举的前面,您仍然可以获得“作用域”的好处!


在此处测试上面的代码:https://onlinegdb.com/SJQ7uthcP