配列の添字に整数を直接使用すると、その要素が何を意味するのか不明確になりやすい。 列挙型を活用することで、配列の添字やサイズ管理の可読性や整合性を向上させやすい。
添字に列挙型を使用する利点
- 意味の明確化: 整数値の代わりに列挙子を使用することで、どの要素にアクセスしているかが一目でわかる。
- 暗黙の型変換: 未スコープ列挙型(unscoped enum)は整数型に暗黙的に変換されるため、
std::size_tを期待する配列の添字としてそのまま利用できる。 - 符号問題の回避: 列挙子は暗黙的に
constexprであるため、添字で使うときの符号変換に関する警告を避けやすくなる。
「Count Enumerator」による管理
列挙型の最後に max_items や count といった名前の列挙子を追加する手法。
- サイズの自動同期: デフォルト値を使用している場合、末尾の列挙子はそれより前の要素数と一致する値を保持する。
- メンテナンス性: 新しい要素を
max_列挙子の前に追加するだけで、それを利用している配列のサイズ(std::vector<int> scores(max_students)など)も自動的に更新される。 - 整合性の検証:
static_assert(std::array用)やassert(std::vector用)を使用して、配列の初期化リストの要素数が列挙型のカウントと一致しているかを確認するのがベストプラクティス。
符号関連の注意点と対策
- 非 constexpr 変数の罠: 列挙型の変数を非
constexprで定義して添字に使う場合、基底型が符号付きだとコンパイラが警告を出すことがある。 - 基底型の指定: 列挙型の定義時に
enum Names : unsigned intのように基底型を明示的に符号なし整数に指定することで、この問題を解決できる。
Enum Class(スコープ付き列挙型)の利用
enum class は名前空間を汚染しないため有用だが、整数への暗黙変換が行われないという制約がある。
- 変換の手間: 添字として使うには毎回
static_cast<std::size_t>(...)が必要になり、コードが煩雑になる。 - 単項 + 演算子の活用: 単項
+演算子をオーバーロードして基底型を返すように定義することで、v[+StudentNames::stan]のように簡潔に記述するテクニックがある。 - 構成の一例: 変換が頻繁に発生する場合は、名前空間(
namespace)の中に通常のenumを配置する構成も、記述性とのバランスが取りやすい。
例
#include <iostream>
#include <vector>
#include <string>
#include <cassert>
#include <type_traits> // std::underlying_type_t 用
// 1. 名前空間と未スコープ列挙型を組み合わせる(推奨される構成)
// 列挙子が名前空間を汚染するのを防ぎつつ、整数への暗黙変換を利用できる
namespace Students {
enum Names {
kenny,
kyle,
stan,
butters,
cartman,
// 新しい生徒はここに追加する
max_students // 常に最後に置き、要素数のカウントとして利用する
};
}
// 2. Enum Class を使う場合のテクニック
enum class Animal {
chicken,
dog,
cat,
max_animals
};
// Enum class は暗黙変換されないため、単項 + 演算子をオーバーロードして
// 整数型(基底型)へ変換しやすくする手法
constexpr auto operator+(Animal a) noexcept {
return static_cast<std::underlying_type_t<Animal>>(a);
}
int main() {
// --- 基本的な使い方 ---
// max_students (5) を使って vector のサイズを自動決定
std::vector<int> testScores(Students::max_students);
// 整数インデックス(testScores[2])よりも意味が明確になる
testScores[Students::stan] = 76;
std::cout << "The class has " << Students::max_students << " students.\n";
// --- 整合性のチェック ---
std::vector<std::string> studentNames { "Kenny", "Kyle", "Stan", "Butters", "Cartman" };
// 列挙型の数と初期化リストの数が一致しているか確認する(ベストプラクティス)
assert(std::size(studentNames) == Students::max_students);
// --- Enum Class でのアクセス ---
std::vector<int> legs(+Animal::max_animals);
legs[+Animal::dog] = 4; // オーバーロードした + により、static_cast なしで記述可能
return 0;
}