💡 要約:C++の列挙型チュートリアル:古典的なenumの落とし穴、C++11のenum class、クラス内enum、そしてC++26リフレクション(std::meta)がマクロやルックアップテーブルなしでenumからstringへの変換をどう解決するか。
最終更新:2026-10-24
C++ enum 列挙型¶
著者について:¶
個人公式アカウント:

伝統的な挙動¶
列挙には次のような問題があります:
- スコープが制限されず、名前の衝突を起こしやすいです。例えば以下はコンパイルを通れません:
enum Color
{
RED,
BLUE
};
enum Feeling
{
EXCITED,
BLUE
};
int main()
{
Color a = BLUE; // error
Feeling b = EXCITED;
std::cout << a << ":" << b << std::endl;
return 0;
}
具体的な実装はこちら:tradition_color.cpp
定番のやり方¶
スコープが制限されないことによる名前の衝突問題を解決する簡単な方法のひとつは、列挙変数に名前を付けるときにプレフィックスを付けることです。例えば上の例を COLOR_BLUE や FEELING_BLUE に変更します。
一般に、一貫性のためにすべての定数にプレフィックスを統一して付けます。しかしこれでは列挙変数を定義するコードが煩わしくなります。Cプログラムではそうせざるを得ないかもしれません。しかしC++プログラマはおそらくこの方法を好まないでしょう。代替案は名前空間です:
こうすれば Color::Type c = Color::RED; として新しい列挙変数を定義できます。using namespace Color の後はプレフィックスを省略でき、コードが簡潔になります。ただし、名前空間は後から内容を追加できるため、提供されるスコープの閉じ性は高くありません。大きなプロジェクトでは、異なる人が異なるものに同じ列挙型名を付ける可能性が依然としてあります。
より「効果的」な方法は、クラスまたは構造体でスコープを限定することです。例えば:新しい変数を定義する方法は上の名前空間の場合と同じです。ただしこの場合、クラスが別の場所で内容を変更される心配はありません。ここでクラスではなく構造体を使うのは、これらの定数を公開アクセスさせたいからです。
具体的な実装はこちら:classic_practice.cpp
C++11 の列挙クラス(enum class)¶
上のやり方は1つ目の問題を解決しましたが、残りの2つには手も足も出ません。幸いなことに、C++11標準では「列挙クラス」(enum class)が導入され、上記の問題をうまく解決できます。
- 新しいenumのスコープはもはやグローバルではありません
- 他の型へ暗黙変換できません
/**
* @brief C++11的枚举类
* 下面等价于enum class Color2:int
*/
enum class Color2
{
RED=2,
YELLOW,
BLUE
};
Color2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!
- enumの格納に特定の型を指定できます
enum class Color3:char; // 前向声明
// 定义
enum class Color3:char
{
RED='r',
BLUE
};
char c3 = static_cast<char>(Color3::RED);
具体的な実装はこちら:classic_practice.cpp
クラス内の列挙型¶
ときには、ある定数をクラス内でのみ有効にしたいことがあります。#define で定義されるマクロ定数はグローバルであり、この目的を達成できないため、const で修飾したデータメンバを使うことを思いつきます。const データメンバは確かに存在しますが、その意味は私たちの期待するものではありません。
const データメンバはあるオブジェクトの生存期間中においてのみ定数であり、クラス全体にとっては可変です。クラスは複数のオブジェクトを生成でき、異なるオブジェクトの const データメンバの値は異なり得るからです。
クラス宣言の中で const データメンバを初期化することはできません。以下の使い方は誤りです。クラスのオブジェクトが生成される前は、コンパイラは SIZE の値が何なのか知らないからです。(c++11標準より前)
正しくは、クラスのコンストラクタの初期化リストで行います:
class A
{
A(int size); // 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size) // 构造函数的定义
{
}
A a(100); // 对象 a 的 SIZE 值为 100
A b(200); // 对象 b 的 SIZE 值为 200
では、クラス全体で一定の定数を作るにはどうすればよいのでしょうか?
const データメンバに期待するのはやめて、クラス内の列挙定数を使って実現すべきです。例えば:
class Person{
public:
typedef enum {
BOY = 0,
GIRL
}SexType;
};
//访问的时候通过,Person::BOY或者Person::GIRL来进行访问。
列挙定数はオブジェクトの記憶領域を占有せず、コンパイル時にすべて評価されます。
列挙定数の欠点は、その暗黙のデータ型が整数であり、最大値に限りがあって浮動小数点数を表せないことです。
enum から string へ:C++26リフレクションの答え¶
enumの値をその名前に変換するのは、ほぼすべてのC++プロジェクトが一度はぶつかる問題ですが、C++26まで標準ライブラリには答えがありませんでした。従来の回避策を古い順に:(1)プリプロセッサで列挙子リストを一箇所にまとめるXマクロ。(2)手書きのルックアップテーブルやテンプレート特殊化。enumを追加するたびに表の保守が増えます。(3)magic_enum などのサードパーティライブラリ。コンパイラのトリックで名前を取り出しますが、列挙子の数や基礎型に制約があります。
C++26の静的リフレクション(提案P2996、std::meta)がついにこれを数行の本物のコードにします。1つの関数がコンパイル時に任意のenumのメンバーを列挙し、各列挙子の名前を読み取ります:
#include <meta>
template <typename E>
requires std::is_enum_v<E>
std::string enum_to_string(E value) {
template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
if (value == [:e:]) {
return std::meta::identifier_of(e);
}
}
throw std::invalid_argument("unknown enumerator");
}
これで1つの関数がコードベース全体のenumをカバーします。列挙子のリネームは自動的に文字列へ反映され、不一致は実行時の誤ったラベルではなくコンパイル時に判明します。
正直な注意点:このコードはC++26ドラフトに取り込まれたP2996に従っています。早期のコンパイラプレビュー(EDGやClang explorer)では細かい綴りがまだ流動的なので、実装前にツールチェーンの対応表を確認してください。それまではルックアップテーブルが最も移植性の高いゼロ依存の選択肢です。
よくある質問¶
C++でenumをstringに変換するには?¶
C++26より前は標準的な方法がなく、ルックアップテーブル、テンプレート特殊化、Xマクロ、magic_enum などのライブラリから選ぶことになります。C++26の静的リフレクションはコンパイル時にメンバーを列挙して名前を直接読み取るため、これらの保守コストを一掃します。
標準ライブラリにenumからstringへの変換はありますか?¶
C++26までありません。ストリームと std::to_string は基礎の整数値しか整形しないため、どのプロジェクトも自分用のヘルパーを書くはめになってきました。
C++26リフレクションはいつから使えますか?¶
C++26は次期規格で、リフレクションはドラフト入り済み。EDGやClang explorerでは既にサンプルがコンパイルできます。本番コードではコンパイラの対応状況を追いかけ、ルックアップテーブルをフォールバックとして用意してください。
関連トピック¶
- C++ 純粋仮想関数と抽象クラス | C++那些事
- C++ assert アサーション | C++那些事
- C++ ビットフィールド | C++那些事
- C 言語による C++ 多態の実現 | C++那些事
- C++ const 詳解:定数・const ポインタとポインタ const の違い | C++那些事
- C++ const ファミリー | C++那些事
- C++ 間接参照演算子 * | C++那些事
- C++ 初期化と代入 | C++那些事
- C++ 複数戻り値の書き方 | C++那些事
- C++ mutable キーワード | C++那些事