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

BЈо*_*вић 144 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;
}
Run Code Online (Sandbox Code Playgroud)

a::LOCAL_A是强类型枚举试图实现的,但有一个小的区别:普通枚举可以转换为整数类型,而强类型枚举不能没有强制转换.

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

R. *_*des 141

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

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

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;
Run Code Online (Sandbox Code Playgroud)

  • 根据 [cppreference.com](https://en.cppreference.com/w/cpp/utility/to_underlying),C++23 将有一个 `std::to_underlying()` 函数模板,它完全执行上述操作。 (15认同)
  • 这可能会变得更丑吗?模板让我的眼睛变得十字形,静电投射让我的头发开始着火。标准委员会需要协调一致行动。当然,我可以删除“class”这个词,然后我们又回到了最初让 c/c++ 如此流行的魔力。 (3认同)

小智 120

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

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

因此,不可能将强类型枚举隐式转换为整数,甚至是它的基础类型 - 这就是想法.所以你必须使用static_cast显式转换.

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

希望能帮助到你!

  • 这是来自 C++ 创建者的另一个奇怪的例子,“我们更知道你想做什么”。传统(旧式)枚举有很多好处,例如隐式转换为索引、无缝使用按位运算等。新样式枚举添加了一个非常好的作用域,但是......你不能只使用那个东西(即使使用显式底层类型规范!)。因此,现在您要么被迫使用旧式枚举,并使用一些技巧,例如将它们放入结构中,要么为新枚举创建最丑陋的解决方法,例如围绕 std::vector 创建自己的包装器,以克服 CAST 问题。暂无评论 (25认同)
  • 如果在将 MyEnum::Value1 用作 int 时仍然必须将其强制转换为 int ,那么能够说 enum class MyEnum : int 有什么意义呢?如果您之后仍然必须显式地将其强制转换为该类型,为什么还要能够指定值的类型呢?我不明白。 (23认同)
  • @NicolBolas 即使在 c++11 之前,您也可以通过范围访问常规枚举,但即使在 c++11 中,您也可以在没有范围的情况下访问它们。使用“static_cast”只会使代码看起来丑陋且可读性较差。如果我明确指定基础类型,为什么要这样做?这是旧式枚举的优雅而美丽的解决方案,问题在于它们的范围。新的增加了范围,但消除了二进制掩码和直接值比较的功能。对我来说这很奇怪。这类问题的数量清楚地表明,不仅对我来说 (3认同)
  • @avtomaton:您应该知道常规枚举*也*在 C++11 中具有作用域。是的,它们将枚举器转储到全局范围内(出于向后兼容性的原因),但您可以通过其枚举名称的范围来访问它们。“*为新枚举创建最丑陋的解决方法,例如围绕 std::vector 创建自己的包装器只是为了克服 CAST 问题*”或者...只需输入 `static_cast`。您将使您的代码更有意义。 (2认同)
  • @avtomaton:“*即使在 c++11 之前,您也可以通过作用域访问常规枚举*”[不,您不能。](https://gcc.godbolt.org/z/47bP4K) (2认同)
  • 他们至少可以让“枚举类”能够获得自己的方法。这样我们就可以通过编写简洁的方法来弥补丢失的隐式转换。或者如果我们需要的话,只需添加我们自己的“operator int()”。 (2认同)

Cla*_*ton 67

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);
}
Run Code Online (Sandbox Code Playgroud)

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


更新
这也出现在Scott Meyers的Effective Modern C++中.见第10项(在我的书副本中的项目的最后几页详细说明).

  • 8 字节 -&gt; 8 位? (2认同)

Khu*_*dov 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这个答案需要一些解释和文档。 (4认同)
  • 这不会减少键入或使代码更清晰,并且具有使在大项目中找到这种隐式转换变得更难的副作用.Static_cast比这些结构更容易搜索项目范围. (3认同)
  • @AtulKumar搜索static_cast比搜索to_enum容易吗? (3认同)

iam*_*ind 16

号有没有自然的方式.

实际上,强烈输入enum classC++ 11 背后的动机之一就是阻止它们进行静默转换int.


vis*_*.15 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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这被称为"类型惩罚",虽然有些编译器支持它是不可移植的,因为C++标准说在设置`un.i`之后是"活动成员"并且你只能读取联合的活动成员. (32认同)
  • @JonathanWakely你在技术上是正确的,但我从未见过编译器,它不能可靠地工作.像这样的东西,匿名工会和#pragma曾经是事实上的标准. (6认同)
  • 为什么要使用标准明确禁止的东西,当一个简单的演员会做什么?这是错的. (5认同)
  • 我绝对绝望,有些人认为这种凌乱的未定义行为比简单的“ static_cast”“更具可读性”。 (5认同)
  • 在我的机器上失败,除非我不使用优化。我认为不使用优化对于在语言中做禁止的事情来说是一个很小的代价。 (2认同)

Pix*_*ist 13

在其他答案中给出了缺乏隐式转换(设计)的原因.

我个人使用一元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);
}
Run Code Online (Sandbox Code Playgroud)

这给了很少的"打字开销":

std::cout << foo(+b::B2) << std::endl;
Run Code Online (Sandbox Code Playgroud)

我实际上使用宏来创建枚举和操作符一次性运行.

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


sol*_*333 11

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

  • 它没有添加任何类型安全(实际上,您刚刚删除了类型安全) - 它只添加了名称范围。 (7认同)

Ann*_*inn 7

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
}
Run Code Online (Sandbox Code Playgroud)

  • 哦...我只是想到了使用结构而不是命名空间的原因,它将防止您意外地定义相同的枚举“命名空间”两次。例如,如果“TextureUploadFormat”是一个命名空间,您可能会多次使用该命名空间,并意外地合并两个枚举的命名空间。 (2认同)

Col*_*iot 6

对于本机来说这似乎是不可能的enum class,但可能你可以用以下方法模拟enum classa class:

在这种情况下,

enum class b
{
    B1,
    B2
};
Run Code Online (Sandbox Code Playgroud)

相当于:

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;
  }
};
Run Code Online (Sandbox Code Playgroud)

这大致相当于原版enum class.您可以b::B1在具有返回类型的函数中直接返回b.你可以switch case用它等

在本示例的精神中,您可以使用模板(可能与其他东西一起)来概括和模拟enum class语法定义的任何可能的对象.


Atu*_*mar 5

正如许多人所说,没有办法在不增加开销和太多复杂性的情况下自动转换,但是如果在某个场景中使用了一些强制转换,您可以通过使用 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;
    }
}
Run Code Online (Sandbox Code Playgroud)