C++20のコンパイル時 Enum名取得
C++20 から <source_location>
と言う標準ライブラリが実装された、この機能はコンパイル時/実行時にソースコードの位置の名前を取得すると言うものである。
これを悪用することで、enum の名前を取得することができるようになる。
基本的には __FUNCTION__
や __PRETTY_FUNCTION__
として定義される識別子を用いた方法と変わらないが、ここではもっと簡潔に書いたバージョンを紹介する。また、 __FUNCTION__
や __PRETTY_FUNCTION__
と違って処理系定義ではないので、 MSVC などのコンパイラーでも動く。
ちなみに source_location
の function_name
で帰ってくる返り値は処理系定義なので、 clang
以外のコンパイラーで動くことは保証できない。
If this object represents a position in a body of a function, returns an implementation-defined null-terminated byte string corresponding to the name of the function.(cppreference)
実装
#include <source_location>
#include <string_view>
template <auto T>
consteval auto GetEnumName() {
auto fn = std::string_view(std::source_location::current().function_name());
auto p = fn.find("T = ");
return fn.substr(p + 4, fn.size() - p - 5);
};
template <class T, int First, int Last>
auto GetEnumName(T v) {
std::string_view en;
[&v, &en]<auto... I>(std::index_sequence<I...>) {
(void)((en = GetEnumName<static_cast<T>(I)>(),
static_cast<int32_t>(v) == I) ||
...);
}(std::make_index_sequence<Last - First + 1>{});
return en;
}
Compiler Explorer)
使用例 (例1 コンパイル時に名前を取得する
#include <print>
enum class Test {
Foo,
Bar,
Bazz,
}
auto main() -> int {
std::println(GetEnumName<Test::Foo>()); // -> Test::Foo
std::println(GetEnumName<Test::Bazz>()); // -> Test::Bazz
}
例2 実行時に名前を取得する
#include <print>
enum class Alphabet {
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
};
constexpr auto GetAlphabetName = []<class T>(T v) {
return GetEnumName<T, 0, 25>(std::forward<T>(v));
};
auto main() -> int {
std::println("{}", GetAlphabetName(Alphabet::A)); // -> Alphabet::A
std::println("{}", GetAlphabetName(Alphabet::B)); // -> Alphabet::B
std::println("{}", GetAlphabetName(Alphabet::C)); // -> Alphabet::C
std::println("{}", GetAlphabetName(Alphabet::Z)); // -> Alphabet::Z
return 0;
}
コンパイル時の境界チェック
C++ の規格に基づくと列挙型範囲外へのキャストは未定義動作である。([expr.static.cast])
したがって、使用は推奨しない。実際に llvm ではコンパイル時のキャストは不適格そして、実行時のコンテキストでも Warning になろうとしている。
実装
以下は 0 始まりの連続した値を取ると仮定し、 0 から underlying_type_t
の最大の値まで二分探索を行うコードである。
template <class T, std::underlying_type_t<T> L = 0,
std::underlying_type_t<T> H =
std::numeric_limits<std::underlying_type_t<T>>::max(),
bool Enabled = true>
struct UpperBound {
static constexpr bool found =
!GetEnumName<static_cast<T>((L + H) / 2)>().contains('(');
static constexpr int value =
(H - L < 2) ? L
: UpperBound<T, L, (L + H) / 2, !found>::value +
UpperBound<T, (L + H) / 2, H, found>::value;
};
template <class T, std::underlying_type_t<T> L, std::underlying_type_t<T> H>
struct UpperBound<T, L, H, false> {
static constexpr std::underlying_type_t<T> value = 0;
};
これを用いれば以下のように GetEnumName
を記述することができる
template <class T, int First = 0, int Last = UpperBound<T>::value>
auto GetEnumName(T v) {
std::string_view en;
[&v, &en]<auto... I>(std::index_sequence<I...>) {
(void)((en = GetEnumName<static_cast<T>(I)>(),
static_cast<int32_t>(v) == I) ||
...);
}(std::make_index_sequence<Last - First + 1>{});
return en;
}
Discussion
処理系が独自に提供している識別子という意味ならば
__FUNCTION__
や__PRETTY_FUNCTION__
などを意図していますか?似たような名前の
__func__
は C99 および C++11 で標準化されているのでどの処理系でも使えるべきですが…__FUNC__
は__FUNCTION__
や__PRETTY_FUNCTION__
を意図していますね。こんがらがってしまっていたので修正します。ありがとうございます!ちなみに
__func__
はテンプレートパラメーターの型の名前とかを含まないのでこのようなトリックを使うことはできないんですよね。