昔からあるテクニックではありますが、比較的新しい文法で書き直した&gcc,clang,vcに対応したのでせっかくなのでこちらに置いておこうと思います。機能は最小限なのでもし拡張したい場合は頑張ってください。バグがあったらすみません。
// 指定したenum型Tに関する情報を取り出せるクラス
// 名前空間で囲むと動きません
template<typename T> requires(std::is_enum_v<T>)
class enum_traits
{
template<T v>
static constexpr auto func_sig()
{
#ifdef _MSC_VER
return __FUNCSIG__;
#else
return __PRETTY_FUNCTION__;
#endif
}
public:
using underlying_type = std::underlying_type_t<T>;
// T型の値vの文字列を取得
// 戻り値型はarray<char>でヌル文字まで含みます
// enumに存在しない値を指定されるとarray<char,1>でヌル文字だけ返します
template<T v>
static constexpr decltype(auto) name() {
// いったん関数シグネチャをstring_viewにして加工し必要な部分だけを抜き出します
auto process = []() -> std::string_view {
const std::string_view name{ func_sig<v>() };
auto i = name.find("enum_traits") + 12;
auto skipTypeParam = [&](size_t i) {
for (uint32_t pare = 1; pare; ++i)
{
if (name[i] == '>')pare--;
else if (name[i] == '<')pare++;
}
return i;
};
i = skipTypeParam(i);
if (name.substr(i, 14) == "::func_sig() [")
{
i += 14;
if (name.substr(i, 4) == "with")
{
// gcc
if (name[i + 11] == '(')
{
return {};
}
auto end = name.find(';', i);
i = name.find_last_of("::", end) + 1;
return name.substr(i, end - i);
}
else
{
//clang
auto end = name.size() - 1;
for (i = end - 1; name[i] >= '0' && name[i] <= '9'; i--);
if (name[i] == ')')return {};
i = name.find_last_of("::", end) + 1;
return name.substr(i, end - i);
}
}
else
{
// msvc
i += 11;
if (name[i] == '(')return {};
i = name.find_last_of("::") + 1;
return name.substr(i, name.size() - 7 - i);
}
};
// string_viewをarray<char>に直すことでコンパイル時定数として扱えるようになる上に
// enum値の名前の部分以外の文字列がビルド結果に残らないようにしてます
constexpr auto str = [](auto process) {
std::array<char, [](auto p) {return p().size(); }(process)+1 > buf;
auto str = process();
*std::copy(str.begin(), str.end(), buf.data()) = 0;
return buf;
}(process);
return str;
}
// enum型が0から連続した値しかない前提になってます
// enum値の最大値を探します
template<underlying_type v = 0>
static constexpr underlying_type getMax()
{
if constexpr (name<static_cast<T>(v)>().size() == 1)
return v;
else
return getMax<v + 1>();
}
// 上記の定数版
static constexpr underlying_type max = getMax();
// 名前配列
// ジャグ配列なのでポインタを扱う性質上コンパイル時定数にはならない
static inline auto names = [] {
std::array<std::string_view, max> tmp;
[&] <underlying_type ...i>(std::integer_sequence<underlying_type, i...>) {
((tmp[i] = [](auto) -> auto& {
// ここに文字列の実体を置いてます
static constexpr auto NAME = name<static_cast<T>(i)>();
return NAME;
}(i).data()), ...);
}(std::make_integer_sequence<underlying_type, max>());
return tmp;
}();
// 値配列
static constexpr auto values = [] {
std::array<T, max> tmp;
[&] <underlying_type ...i>(std::integer_sequence<underlying_type, i...>) {
((tmp[i] = static_cast<T>(i)>()), ...);
}(std::make_integer_sequence<underlying_type, max>());
return tmp;
}();
// enum値から文字列に変換します
static std::string_view to_string(T value)
{
return names[static_cast<underlying_type>(value)];
}
};
// 文字列化を簡単にする補助関数
template<typename T> requires(std::is_enum_v<T>)
inline auto to_string(T value)
{
return enum_traits<T>::to_string(value);
}