1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【小ネタ】c++ template&constexprでenumを文字列に変換

Last updated at Posted at 2023-03-06

昔からあるテクニックではありますが、比較的新しい文法で書き直した&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);
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?