🤹

我々がenumに望むこと

2025/01/02に公開

今回は逆に 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 以降に追加されることを祈りましょう。


|cpp記事一覧へのリンク|

Discussion