如何在C++11中输出枚举类的值

140

在C++11中,我如何输出enum class的值?在C++03中是这样的:

#include <iostream>

using namespace std;

enum A {
  a = 1,
  b = 69,
  c= 666
};

int main () {
  A a = A::c;
  cout << a << endl;
}

在C++0x中,这段代码不能编译

#include <iostream>

using namespace std;

enum class A {
  a = 1,
  b = 69,
  c= 666
};

int main () {
  A a = A::c;
  cout << a << endl;
}


prog.cpp:13:11: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream<char>&&'
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/ostream:579:5: error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = A]'

Ideone.com 编译完成


1
为什么你要尝试输出枚举?enum类用于不混淆枚举值和int表示。 - RiaD
@RiaD 如果你在 switch 语句中评估一个枚举类的值,并想要在默认分支中捕获未处理的值,例如打印未处理的值,该怎么办? - stackprotector
@stackprotector 嗯,我在过去的9年中学到了一些需要使用它的用例 :) 当然,你的是其中最重要的。 - RiaD
致敬StackOverflow。 - lishrimp
9个回答

176

与未加限定作用域的枚举不同,加限定作用域的枚举类型不能 隐式地 转换为其整数值。你需要使用强制转换 显式地 将其转换为整数:

std::cout << static_cast<std::underlying_type<A>::type>(a) << std::endl;

你可能想将逻辑封装到一个函数模板中:

template <typename Enumeration>
auto as_integer(Enumeration const value)
    -> typename std::underlying_type<Enumeration>::type
{
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

用作:

std::cout << as_integer(a) << std::endl;

5
为什么要使用尾置返回类型语法? - Nicol Bolas
3
@NicolBolas:我从我的开源库CxxReflect中复制了as_integer函数(请参见enumeration.hpp)。该库在任何地方都一致地使用尾返回类型,以保持一致性。 - James McNellis
21
虽然有些晚了2年,但如果其他人看到这个问题,你可以使用上述的cast技术方法,并简单地调用"static_cast<int>(value)"来获得整数或者"static_cast<A>(intValue)"来获得枚举值。请记住,从整数到枚举或者从枚举到枚举可能会引起问题,并且通常是设计错误的标志。 - Benjamin Danger Johnson
5
int(value) 和 A(intValue) 也可以工作,而且没有难看的尖括号。 - Grault
6
as_integer 可以被定义为 constexpr,以便在需要常量表达式的情况下使用。 - Nawaz
显示剩余4条评论

50
#include <iostream>
#include <type_traits>

using namespace std;

enum class A {
  a = 1,
  b = 69,
  c= 666
};

std::ostream& operator << (std::ostream& os, const A& obj)
{
   os << static_cast<std::underlying_type<A>::type>(obj);
   return os;
}

int main () {
  A a = A::c;
  cout << a << endl;
}

我完全复制了这个例子,并使用g++ -std=c++0x enum.cpp编译,但是我得到了一堆编译器错误 -> http://pastebin.com/JAtLXan9。我也无法编译@james-mcnellis的示例。 - Dennis
4
@Dennis 在C++11中,underlying_type是可用的。请注意,此函数只能在C++11或更新版本的标准中使用。 - Deqing
@Deqing enum class同样适用。 - stackprotector

24

通过使用与非作为的std::enable_if,编写一个通用的 operator<< 函数可以使您的第二个示例(即使用作用域枚举的示例)像非作用域枚举一样工作。此解决方案是通用的,对于所有作用域枚举都有效,而不是为每个作用域枚举编写代码(如由@ForEveR提供的答案中所示)。

解决方案是编写一个通用的operator<<函数,适用于任何作用域枚举。该解决方案通过SFINAEstd::enable_if实现。

#include <iostream>
#include <type_traits>

// Scoped enum
enum class Color
{
    Red,
    Green,
    Blue
};

// Unscoped enum
enum Orientation
{
    Horizontal,
    Vertical
};

// Another scoped enum
enum class ExecStatus
{
    Idle,
    Started,
    Running
};

template<typename T>
std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream, const T& e)
{
    return stream << static_cast<typename std::underlying_type<T>::type>(e);
}

int main()
{
    std::cout << Color::Blue << "\n";
    std::cout << Vertical << "\n";
    std::cout << ExecStatus::Running << "\n";
    return 0;
}

这个方案在clang下运行良好,但在gcc 4.9.2下,当链接<<时,此解决方案会失败,并显示错误error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’。这似乎是因为当流是临时的时,ADL失败了,上述模板不可用。有什么建议吗? - ofloveandhate
@ofloveandhate,您能提供一个产生这个问题的示例链接吗?我在gcc 4.9.2中测试了上面的代码,没有任何问题,只进行了一些微小的更改,我通过将3个"cout"语句连在一起来使其成为一个单一的"cout"语句。请参见此处 - James Adkison
起初我很难让上述解决方案在流式类枚举到 Boost.Log 轻量级记录器中工作。解决方案是:using blos = boost::log::record_ostream; template<typename T> blos& operator<<(typename std::enable_if<std::is_enum<T>::value, blos>::type& stream, const T& e) { return stream << static_cast<typename std::underlying_type<T>::type>(e); } - ofloveandhate
不,完全不是。这正是重点所在。Boost.Log文档表明,只需要为std::ostream重载<<,但对于类枚举,我发现这是不够的。相反,我不得不为我提到的“blos”添加一个重载。我想分享一下,以防其他人遇到此问题。 - ofloveandhate
在我看来,这是最好的答案。使用clang对我很有效。 - Oscar Hierro
显示剩余10条评论

21

为了更简单地写作,

enum class Color
{
    Red = 1,
    Green = 11,
    Blue = 111
};

int value = static_cast<int>(Color::Blue); // 111

当枚举类型明确给定基础类型时,这将无法正常工作。 - James

11

(我还没有发表评论的权限。)我建议对James McNellis已经很好的答案进行以下改进:

template <typename Enumeration>
constexpr auto as_integer(Enumeration const value)
    -> typename std::underlying_type<Enumeration>::type
{
    static_assert(std::is_enum<Enumeration>::value, "parameter is not of type enum or enum class");
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

使用constexpr可以让我将枚举成员值作为编译时数组大小使用。

使用static_assert+is_enum,可以在编译时“确保”函数只处理枚举类型,就像建议的那样。

顺便问一下自己:当我想要为我的枚举成员分配数字值时,为什么应该使用enum class?考虑到转换的工作量,也许我会回到普通的enum,就像我在这里提出的:如何在C++中将枚举用作标志?


基于@TobySpeight的建议,这是另一种(更好的)无需static_assert的写法:

template <typename Enumeration>
constexpr std::enable_if_t<std::is_enum<Enumeration>::value,
std::underlying_type_t<Enumeration>> as_number(const Enumeration value)
{
    return static_cast<std::underlying_type_t<Enumeration>>(value);
}

是否存在一种类型 T,使得 std::underlying_type<T>::type 存在,但 std::is_enum<T>::value 为 false?如果不存在,则 static_assert 没有任何价值。 - Toby Speight
1
我没有在所有的编译器上进行测试。但是,@TobySpeight 你可能是对的,msvc2013似乎会输出可理解的错误信息,表明底层类型_t存在和类型本身为枚举类型之间存在1对1的对应关系。而且静态断言甚至都没有被触发。但是:参考文献指出,如果未提供完整的枚举类型,则底层类型的行为是未定义的。因此,静态断言只是希望在需要时获得最大程度的可理解消息。也许有可能强制它尽早/最早地被处理? - yau
啊,是的,如果“Enumeration”不是完整的枚举类型,则它是未定义的。在这种情况下,由于它用于返回类型,可能已经太晚了。也许我们可以指定std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>作为返回类型?当然,如果您有支持概念的编译器,那么这将更加容易(并且错误消息更清晰)... - Toby Speight
1
为了避免安全关键代码中的任何意外缩小转换(并使静态分析工具满意),我想指定确切类型。因此,为什么不用enum类来为枚举成员分配数字值呢? - Fred Schoen

3

以下内容适用于C++11:

template <typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value,
                                  typename std::underlying_type<Enum>::type>::type
to_integral(Enum const& value) {
    return static_cast<typename std::underlying_type<Enum>::type>(value);
}

3
自c++23起,有std::to_underlying函数,它与其他所有答案的功能相同,但它在std中。

2
应该给出一个例子。 - parsley72
static_cast<typename std::underlying_type<T>::type>(e); is same as std::to_underlying(e); - mug896
...这是我在答案中明确说明的。 - RiaD

1

在James McNellis出色的回答基础上,如果你的编译器支持C++20中引入的概念和约束,可以使用它来以更直观的方式为函数模板引入额外的编译时检查(即更清晰地指示任何不正确的用法):

template<typename E>
concept EnumType = std::is_enum_v<E>;

template <EnumType E>
constexpr std::underlying_type_t<E> enumUnderlyingType(E const underlying_type)
{
    return static_cast<std::underlying_type_t<E>>(underlying_type);
}

-2
你可以像这样做:
//outside of main
namespace A
{
    enum A
    {
        a = 0,
        b = 69,
        c = 666
    };
};

//in main:

A::A a = A::c;
std::cout << a << std::endl;

3
这个问题询问了一个枚举类。 - Ant

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接