#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是强类型enum试图实现的目标,但有一个小小的区别:普通enum可以转换为整数类型,而强类型enum如果没有强制转换就不能做到这一点。

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


当前回答

An extension to the answers from R. Martinho Fernandes and Class Skeleton: Their answers show how to use typename std::underlying_type<EnumType>::type or std::underlying_type_t<EnumType> to convert your enumeration value with a static_cast to a value of the underlying type. Compared to a static_cast to some specific integer type, like, static_cast<int> this has the benefit of being maintenance friendly, because when the underlying type changes, the code using std::underlying_type_t will automatically use the new type.

然而,这有时不是你想要的:假设你想直接打印出枚举值,例如std::cout,如下例所示:

enum class EnumType : int { Green, Blue, Yellow };
std::cout << static_cast<std::underlying_type_t<EnumType>>(EnumType::Green);

如果你稍后将底层类型更改为字符类型,例如uint8_t,那么EnumType::Green的值将不会作为数字打印,而是作为字符打印,这很可能不是你想要的。因此,您有时更愿意将枚举值转换为类似于“底层类型,但在必要时使用整数提升”的类型。

如果需要,可以对强制转换的结果应用一元运算符+来强制提升整数。然而,你也可以使用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);

其他回答

由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_底层函数:

#include <utility>

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

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

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

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

对于本地枚举类,这似乎是不可能的,但也许你可以用类来模拟枚举类:

在这种情况下,

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类。你可以在返回类型为b的函数中直接返回b::B1 for。你可以用它来切换大小写,等等。

根据本例的精神,您可以使用模板(可能与其他东西一起使用)泛化和模拟枚举类语法定义的任何可能的对象。

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

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

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

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;