Featured image of post C++ 打印enum class枚举类

C++ 打印enum class枚举类

在 C++11中,由于枚举类(enum class)无法通过 std::cout 标准流打印,为了解决这个问题,提供了三种解决方案。

# Preface

In C++11, the strongly typed enum class type (also known as enumeration class) was introduced. However, there is an awkward issue that std::cout, the standard stream, does not support outputting enum class types.

For example, in the following code:

1
2
3
4
5
6
7
8
9
#include <iostream>

int main()
{
    enum class Color { red, green = 20, blue };
    Color r = Color::blue;

    std::cout << r;
}

During compilation, an error will occur. The GCC compilation error message is:

1
2
3
4
5
6
main.cpp:8:14: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'main()::Color')
    8 |     std::cout<<r;
      |     ~~~~~~~~~^~~~
      |          |    |
      |          |     main()::Color
      |          std::ostream {aka std::basic_ostream<char>}

MSVC compilation error message:

1
E0349 No operator "<<" matches these operands

So, how can we print enum class?

# Solutions

# Method One

The underlying type of enum class is actually the int type, so we can simply cast it directly:

1
2
3
4
5
6
7
8
enum class Color { red, green = 20, blue };
Color r = Color::blue;

std::cout << static_cast<int>(r) << std::endl;
// Or
std::cout << static_cast<typename std::underlying_type<Color>::type>(r) << std::endl;

// Output: 21

# Method Two

Casting every time is too troublesome, is there a more convenient way? Of course! You can overload the operator<< operator to achieve this:

1
2
3
4
5
std::ostream& operator << (std::ostream& os, const Color& obj)
{
    os << static_cast<typename std::underlying_type<Color>::type>(obj);
    return os;
}

Now you can directly use std::cout to print without having to cast first.

1
std::cout << r << std::endl;

# Method Three

What if I have multiple enum class, such as enum Color, enum Fruit? Do I have to overload the operator<< operator once for each? So here we use C++ dark magic SFINAE to generically support any enumeration class, not limited to a specific one:

1
2
3
4
5
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);
}

Example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
#include <type_traits>

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

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

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";
    return 0;
}

Defects

For enumeration classes within a namespace, they cannot be printed, meaning that the following E cannot be recognized:

1
2
3
namespace nn{
enum class E {}
}

# Summary

In C++11, due to the problem that enumeration class (enum class) cannot be directly output to the std::cout standard stream, three solutions are provided to solve this issue.

  1. The first solution is to convert the enumeration class to its underlying int type through a forced type cast, and then output the int value.
  2. The second solution is to overload the operator<< operator to convert the enumeration class to its underlying type before outputting.
  3. The third solution is to use C++’s SFINAE technique to generically support any enumeration class and convert it to its underlying type before outputting.

These solutions can all address the issue of not being able to directly output enumeration classes, providing different choices and flexibility.

Licensed under CC BY-NC-SA 4.0