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


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

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

Answers:


134

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

  1. 提供类型安全性,从而避免通过整数提升而隐式转换为整数。
  2. 指定基础类型。
  3. 提供强大的作用域。

因此,不可能将强类型的枚举隐式转换为整数,甚至是其基础类型-这就是这个想法。因此,您必须使用static_cast明确显示转换。

如果唯一的问题是作用域确定,并且您确实希望隐式提升为整数,那么最好在声明该结构的范围内使用不强类型的枚举。


2
这是C ++创造者“我们更了解您想做什么”的另一个奇怪的例子。常规(旧式)枚举有很多好处,例如隐式转换为索引,无缝使用按位操作等。新式枚举添加了一个非常好的作用域范围,但是...您不能仅使用该范围(即使使用显式的基础类型规范!)。因此,现在您要么被迫将旧式枚举与诸如将它们放入struct之类的技巧一起使用,要么被迫为新枚举创建最丑陋的变通办法,例如围绕std :: vector创建自己的包装器,只是为了克服CAST问题。没有评论
avtomaton

152

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

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

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;

75

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


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

3
这不会减少打字或使代码更整洁,并且具有使在大型项目中更难找到此类隐式转换的副作用。与这些结构相比,Static_cast在整个项目中搜索都容易。
Atul Kumar

3
@AtulKumar搜索static_cast比搜索to_enum容易吗?
约翰·杰雷尔

1
该答案需要一些说明和文档。
Lightness Races in Orbit

17

号有没有自然的方式

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


看看库尔希德·诺拉莫多夫(Khurshid Normuradov)的回复。它是“自然的方式”,与“ C ++编程语言(第4版)”中的目的很相似。它不是以“自动方式”来实现的,这很好。
PapaAtHome 2015年

@PapaAtHome我不明白static_cast带来的好处。键入或代码清洁度没有太大变化。这是什么自然的方式?函数返回值?
Atul Kumar

1
@ user2876962对我来说,好处是它不是Iammilind所说的自动或“静音”的。这样可以防止难以发现错误。您仍然可以进行演员表转换,但是您不得不考虑一下。这样,您就知道自己在做什么。对我来说,这是“安全编码”习惯的一部分。我更喜欢没有转换不是自动完成的,因为它极有可能引入错误。如果您问我,与类型系统相关的C ++ 11的很多变化都属于此类。
PapaAtHome 2015年

17

其他答案中给出了不进行隐式转换(通过设计)的原因。

我个人使用一元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); }

13

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

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

33
这称为“类型修剪”,尽管某些编译器支持这种移植,但这种移植不是可移植的,因为C ++标准指出,设置后即un.i为“活动成员”,并且您只能读取联合的活动成员。
Jonathan Wakely 2014年

6
@JonathanWakely从技术上来说,您是正确的,但我从未见过无法可靠运行的编译器。诸如此类的东西,匿名联合和#pragma曾经是事实上的标准。
BigSandwich 2015年

5
当简单的强制转换可以使用时,为什么要使用标准明确禁止的内容?这是错误的。
Paul Groke '16

1
在技​​术上是否正确,对我来说,比这里找到的其他解决方案更具可读性。而且对我来说更重要的是,它不仅可以用来解决序列化问题,而且可以轻松,可读地解决枚举类的反序列化问题。
MarcinWaśniowski17年

6
我绝对绝望,有些人认为这种凌乱的未定义行为比简单的行为“更具可读性” static_cast
underscore_d

13

简短的答案是您不能如上述帖子所指出的那样。但就我而言,我只是不想使名称空间混乱,但仍具有隐式转换,所以我做了:

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

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


3
它不增加任何类型安全性(实际上,您刚刚删除了类型安全性)-它仅添加名称作用域。
Lightness Races in Orbit,

@LightnessRacesinOrbit是的,我同意。我撒了谎。确切地说,从技术上讲,该类型位于名称空间/作用域的下方,并且完全限定为Foo::Foo。成员可以通过Foo::bar和进行访问Foo::baz,也可以隐式转换(因此没有太多的类型安全性)。最好总是使用枚举类,尤其是在启动新项目时。
solstice333

6

对于本机而言enum class,这似乎是不可能的,但是您可以enum class使用a 模拟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使用return类型的函数直接返回for b。您可以switch case使用它,等等。

并且,按照本示例的精神,您可以使用模板(可能与其他东西一起使用)来泛化和模拟enum class语法定义的任何可能的对象。


但是B1和B2必须在类之外定义...否则在情况下不可用-header.h <-类b-main.cpp <---- myvector.push_back(B1)
Fl0

?难道不应该是“静态constexpr B”,而不是“静态constexpr INT“,否则,B :: B1是只是根本没有类型安全的int。
有些盖伊

4

正如许多人所说,没有增加开销和太多复杂性的自动转换方法,但是如果在场景中会使用很多强制转换,则可以使用lambda来减少键入量并使外观看起来更好。这将增加一些函数开销调用,但与长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;
    }
}

2

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.