我々がenumに望むこと
今回は逆に C++ に未だにない機能について書きたいと思います。
C# だと以下のコードが書けます。
enum Color {Red = 1, Green = 2, Blue = 3};
public static void Main(string[] args)
{
Color color = Color.Red;
Console.WriteLine($"color: {color}({(int)color})"); // Red(1)
}
C# のEnum
は基本文字列として出力されて、数字として出力するにはキャストしないといけないです。C# は実行時にEnum
の名前と数値の両方にアクセスできるようになっています。
でも C++ のenum
はただの名前が付いている数字で、その名前を出力することができないです。
enum
の名前を出力するには、enum
の数値だけではなく名前の文字列もどこかに保存しないといけなくなります。
便利さを重視している C# と違って、 C++ はパフォーマンスを重視しているので、どのタイプに関しても最低限のメモリーしか使っていないからです。要するに、C++ は C# より低レベルなのです。
そのため、C++ の場合は、enum
を使う時は、以下のように自分で名前をマッピングするしかないです。
enum class Color {Red = 1, Green = 2, Blue = 3};
std::map<Color, const char*> colorNames
{ {Color::Red, "Red"}
, {Color::Green, "Green"}
, {Color::Blue, "Blue"}
};
<<
演算子をオーバーロードすれば、以下のように出力できるようになります。
std::ostream& operator<<(std::ostream& out, Color color)
{
out << colorNames.at(color);
return out;
}
Color color = Color::Red;
std::cout << "color: " << color << "(" << static_cast<int>(color) << ")" << std::endl;
これを毎回各enum
にやらないといけないのは手間ですよね。残念ながら標準ライブラリにはそういう機能がないため、非公式のライブラリを使うしかないです。でも幸い使いやすいenum
のライブラリはたくさん存在します。
例えば、Better Enumsというヘッダーオンリーのライブラリを使えば、以下のように出力できます。
#include <enum.h>
BETTER_ENUM(Color, int, Red = 1, Green, Blue)
Color color = Color::Red;
std::cout << "color: " << color._to_string() << "(" << color.._to_integral() << ")" << std::endl;
こちらは定義がマクロになっていて、違和感があるかもしれないです。
一方、Magic Enumというライブラリの場合は、普通のenum
を定義して、出力したい時に以下のようにマクロを使います。
#include <magic_enum/magic_enum.hpp>
Color color = Color::Red;
std::cout << "color: " << magic_enum::enum_name(color) << "(" << magic_enum::enum_integer(color) << ")" << std::endl;
こちらもヘッダーオンリーのライブラリになっています。
どちらもヘッダーの中身を見てみると、かなり無理矢理のマクロマジックになっていて、本当はお勧めできないですが、標準ライブラリにはまだないため、仕方がないですね。
C++23 以降に追加されることを祈りましょう。
Discussion