简易实现枚举类型到字符串

// 简易实现枚举类型到字符串
#include <array>
#include <string>
#include <utility>
#include <string_view>

template <typename E, E V>
constexpr auto PrettyName()
{
    std::string_view name{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2};
    name.remove_prefix(name.find_last_of(" ") + 1);
    if (name.front() == '(') name.remove_prefix(name.size());
    return name;
}

template <typename E, E V>
constexpr bool IsValidEnum()
{
    return !PrettyName<E, V>().empty();
}

template <int... Seq>
constexpr auto MakeIntegerSequence(std::integer_sequence<int, Seq...>)
{
    return std::integer_sequence<int, (Seq)...>();
}

constexpr auto NormalIntegerSequence = MakeIntegerSequence(std::make_integer_sequence<int, 32>());

template <typename E, int... Seq>
constexpr size_t GetEnumSize(std::integer_sequence<int, Seq...>)
{
    constexpr std::array<bool, sizeof...(Seq)> valid{IsValidEnum<E, static_cast<E>(Seq)>()...};
    constexpr std::size_t count = [](decltype((valid)) v) constexpr noexcept->std::size_t
    {
        auto cnt = std::size_t{0};
        for (auto b : v) if (b) ++cnt;
        return cnt;
    }(valid);
    return count;
}

template <typename E, int... Seq>
constexpr auto GetAllValidValues(std::integer_sequence<int, Seq...>)
{
    constexpr std::size_t count = sizeof...(Seq);
    constexpr std::array<bool, count> valid{IsValidEnum<E, static_cast<E>(Seq)>()...};
    constexpr std::array<int, count> seq{Seq...};
    std::array<int, GetEnumSize<E>(NormalIntegerSequence)> values{};

    for (std::size_t i = 0, v = 0; i < count; ++i) if (valid[i]) values[v++] = seq[i];
    return values;
}

template <typename E, int... Seq>
constexpr auto GetAllValidNames(std::integer_sequence<int, Seq...>)
{
    constexpr std::array<std::string_view, sizeof...(Seq)> names{PrettyName<E, static_cast<E>(Seq)>()...};
    std::array<std::string_view, GetEnumSize<E>(NormalIntegerSequence)> validNames{};

    for (std::size_t i = 0, v = 0; i < names.size(); ++i) if (!names[i].empty()) validNames[v++] = names[i];
    return validNames;
}

template <typename E>
constexpr std::string_view Enum2String(E V)
{
    constexpr auto names = GetAllValidNames<E>(NormalIntegerSequence);
    constexpr auto values = GetAllValidValues<E>(NormalIntegerSequence);
    constexpr auto size = GetEnumSize<E>(NormalIntegerSequence);

    for (size_t i = 0; i < size; ++i) if (static_cast<int>(V) == values[i]) return names[i];
    return std::to_string(static_cast<int>(V));
}

 

这段代码是一套编译期枚举反射(Enum Reflection)工具,核心目标是在编译期实现枚举类型的 “值→字符串” 转换、枚举有效值统计、枚举名称提取等反射能力,无需运行时查表或手动维护字符串映射表。
 

核心设计目标

C++ 原生不支持枚举的反射(无法直接通过枚举值获取其字面量名称),这套代码通过编译器内置宏(__PRETTY_FUNCTION__)和编译期计算(constexpr),在编译阶段解析枚举值的名称、统计有效枚举值数量、生成 “枚举值 - 名称” 映射,最终实现枚举值到字符串的高效转换。
 

关键函数 / 模板的设计含义拆解

1. PrettyName<E, V>():编译期提取枚举值的字面量名称

template <typename E, E V>
constexpr auto PrettyName()
{
    std::string_view name{__PRETTY_FUNCTION__, sizeof(__PRETTY_FUNCTION__) - 2};
    name.remove_prefix(name.find_last_of(" ") + 1);
    if (name.front() == '(') name.remove_prefix(name.size());
    return name;
}
  • 核心依赖:__PRETTY_FUNCTION__ 是编译器内置宏,会展开为当前函数的 “美化函数名”(包含模板参数)。
    例如,实例化 PrettyName<Color, Color::Red>() 时,__PRETTY_FUNCTION__ 可能展开为:
    constexpr auto PrettyName() [with E = Color; E V = Color::Red]
  • 逻辑:
    1. 截取 __PRETTY_FUNCTION__ 的有效部分(去掉末尾的空字符等);
    2. 从最后一个空格后截取字符串(提取枚举值的字面量,如 Color::Red);
    3. 处理异常情况(若截取结果以 ( 开头,说明无有效名称,返回空)。
  • 作用:编译期获取枚举值 V 的字面量名称(如 Color::Red)。

2. IsValidEnum<E, V>():判断枚举值是否有效

template <typename E, E V>
constexpr bool IsValidEnum()
{
    return !PrettyName<E, V>().empty();
}
  • 逻辑:若 PrettyName 返回非空字符串,说明 V 是枚举 E 的有效枚举值;否则为无效值(如手动强转的非法值)。
  • 作用:筛选出枚举类型 E 的 “合法” 枚举值。

3. MakeIntegerSequence & NormalIntegerSequence:生成整数序列

template <int... Seq>
constexpr auto MakeIntegerSequence(std::integer_sequence<int, Seq...>)
{
    return std::integer_sequence<int, (Seq)...>();
}

constexpr auto NormalIntegerSequence = MakeIntegerSequence(std::make_integer_sequence<int, 32>());
  • 逻辑:生成一个包含 0~31 的整数序列(std::integer_sequence<int, 0,1,2,...,31>)。
  • 作用:枚举值通常是整数类型(默认从 0 开始),用这个序列遍历 “可能的枚举值范围”(这里限定 32 个,可扩展),用于后续筛选有效枚举值。

4. GetEnumSize<E>():编译期统计有效枚举值数量

template <typename E, int... Seq>
constexpr size_t GetEnumSize(std::integer_sequence<int, Seq...>)
{
    constexpr std::array<bool, sizeof...(Seq)> valid{IsValidEnum<E, static_cast<E>(Seq)>()...};
    constexpr std::size_t count = [](decltype((valid)) v) constexpr noexcept->std::size_t
    {
        auto cnt = std::size_t{0};
        for (auto b : v) if (b) ++cnt;
        return cnt;
    }(valid);
    return count;
}
  • 逻辑:
    1. 遍历 0~31 整数序列,逐个判断 static_cast<E>(Seq) 是否为有效枚举值(生成 valid 布尔数组);
    2. 编译期遍历 valid 数组,统计 true 的数量(即有效枚举值的个数)。
  • 作用:获取枚举类型 E 的有效枚举值总数(如 Color 有 Red/Green/Blue 三个值,则返回 3)。

5. GetAllValidValues<E>():编译期获取所有有效枚举值的整数值

template <typename E, int... Seq>
constexpr auto GetAllValidValues(std::integer_sequence<int, Seq...>)
{
    constexpr std::size_t count = sizeof...(Seq);
    constexpr std::array<bool, count> valid{IsValidEnum<E, static_cast<E>(Seq)>()...};
    constexpr std::array<int, count> seq{Seq...};
    std::array<int, GetEnumSize<E>(NormalIntegerSequence)> values{};

    for (std::size_t i = 0, v = 0; i < count; ++i) if (valid[i]) values[v++] = seq[i];
    return values;
}
  • 逻辑:
    1. 遍历 0~31 整数序列,筛选出其中 “有效枚举值” 对应的整数;
    2. 将这些整数存入编译期数组返回。
  • 作用:生成枚举类型 E 的所有有效枚举值的整数值数组(如 Color 对应 {0,1,2})。

6. GetAllValidNames<E>():编译期获取所有有效枚举值的名称

template <typename E, int... Seq>
constexpr auto GetAllValidNames(std::integer_sequence<int, Seq...>)
{
    constexpr std::array<std::string_view, sizeof...(Seq)> names{PrettyName<E, static_cast<E>(Seq)>()...};
    std::array<std::string_view, GetEnumSize<E>(NormalIntegerSequence)> validNames{};

    for (std::size_t i = 0, v = 0; i < names.size(); ++i) if (!names[i].empty()) validNames[v++] = names[i];
    return validNames;
}
  • 逻辑:
    1. 遍历 0~31 整数序列,为每个值生成对应的枚举名称(PrettyName);
    2. 筛选出非空的名称,存入编译期数组返回。
  • 作用:生成枚举类型 E 的所有有效枚举值的名称数组(如 Color 对应 {"Color::Red", "Color::Green", "Color::Blue"})。

7. Enum2String<E>():枚举值转字符串(核心对外接口)

template <typename E>
constexpr std::string_view Enum2String(E V)
{
    constexpr auto names = GetAllValidNames<E>(NormalIntegerSequence);
    constexpr auto values = GetAllValidValues<E>(NormalIntegerSequence);
    constexpr auto size = GetEnumSize<E>(NormalIntegerSequence);

    for (size_t i = 0; i < size; ++i) if (static_cast<int>(V) == values[i]) return names[i];
    return std::to_string(static_cast<int>(V));
}
  • 逻辑:
    1. 编译期预生成枚举 E 的 “值 - 名称” 映射数组(values 和 names);
    2. 运行时遍历映射数组,找到输入枚举值 V 对应的名称并返回;
    3. 若未找到(非法枚举值),返回其整数值的字符串(如 5)。
  • 作用:对外提供统一的枚举值转字符串接口,编译期完成映射表构建,运行时仅需查表,效率极高。
 

整体设计的核心特点

  1. 编译期计算:所有映射表(值、名称、数量)均在编译期生成,无运行时开销;
  2. 无侵入性:无需修改原有枚举定义,仅需模板实例化即可使用;
  3. 局限性:
    • 依赖编译器的 __PRETTY_FUNCTION__ 格式(不同编译器可能需要适配);
    • 仅支持枚举值为 0~31 的场景(可通过调整 std::make_integer_sequence<int, 32> 的参数扩展);
    • 仅支持 “未指定底层类型 / 底层类型为 int” 的枚举(需适配其他底层类型需修改整数序列类型)。

典型使用场景

enum class Color { Red, Green, Blue };

int main() {
    // 输出 "Color::Red"
    constexpr auto redName = Enum2String(Color::Red);
    // 输出 "3"(非法值)
    auto invalidName = Enum2String(static_cast<Color>(3));
    return 0;
}
 
总结:这套代码是 C++ 中 “无运行时开销的枚举反射” 的典型实现,通过编译器宏和编译期模板计算,填补了 C++ 原生不支持枚举反射的短板,核心价值是自动、高效地实现枚举值到字面量名称的转换。
 
 
posted @ 2025-12-07 23:44  皮卡啰  阅读(2)  评论(0)    收藏  举报