• 参考开源项目实现一个简易的C++枚举转字符串的函数


    前言

    前段时间接触了 magic_enum 这个开源库,代码量不算太多,是一个但头文件的枚举操作库,关于如何使用还写了一篇总结 《推荐一个C++枚举转字符串的开源项目magic_enum》,当时觉得这个库很棒,但是对于我当前枚举转化字符串的需求还说还是太臃肿了,所以决定改造一下,这不今天过来填坑了。

    改造

    一开始还没太理解开源库的原理,认为原来的实现限制太大,为了实现后面字符串转枚举,获取所有枚举名等需求,不得不限定一个枚举的范围,这个范围在 magic_enum 这个开源库中是 [-128, 128],所以当我开始改造时打算把这个范围去掉,但是当我真正弄懂它的原理后,才发现这个范围是必须指定的,不然无法在编译期预处理,从而达到枚举值转换成字符串的目的。

    认识到这一点以后,我也不再纠结范围的限制,设定了一个 [0, 31] 的常用枚举范围,相比于原来 [-128, 128] 的范围缩小了不少,这样能加快编译的速度,参考这个开源库和一些网络上关于这个库的讲解,我也实现了一个功能单一的简洁的枚举转字符串的函数 Enum2String,大约70行代码,使用起来还是比较方便的。

    #include 
    #include 
    #include 
    #include 
    
    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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    函数使用

    这个Enum2String函数使用也非常方便,直接把枚举变量作为参数传进去就可以了:

    #include "myenum.h"
    #include 
    
    enum class Color
    {
        RED,
        GREEN,
        BLUE,
    };
    
    int main()
    {
        Color c = Color::BLUE;
        std::cout << Enum2String(c) << std::endl;
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    编译运行后的结果为:

    $ g++ enumtest.cpp -std=c++17 && ./a.out
    Color::BLUE
    
    • 1
    • 2

    各函数的作用

    前面提到过,我这个库还是参考 magic_enum 这个开源库的源码及网上对它的讲解来实现的,只不过精简了大部分我用不到的内容,仅实现了我想要的枚举转字符串的功能,并且大部分都在编译器求值,仅 Enum2String 函数中遍历的部分只能在运行时才能计算求得,所以效率还算不错,各个模板函数作用明确,下面简单描述下:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    PrettyName() 函数是利用 __PRETTY_FUNCTION__ 这个宏来截取最终我们想要的字符串,如果不做处理,__PRETTY_FUNCTION__ 的值会是这样:

    constexpr auto PrettyName() [with E = Color; E V = Color::BLUE]

    靠近结尾的 Color::BLUE 正是我们想要得到的字符串,所以我们可以按照自己的需要把它截取出来。

    template <typename E, E V>
    constexpr bool IsValidEnum()
    {
        return !PrettyName<E, V>().empty();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    IsValidEnum() 函数是用于判断一个枚举名字是否有效,如果截取的最终名字为空,则认为此枚举无效。

    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>());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    MakeIntegerSequence() 用于生成一个范围是 [0, 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    GetEnumSize() 用于遍历数列范围内的各个整数,找出有效的枚举有多少个。

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    GetAllValidValues() 用于遍历数列范围内各个整数,找出全部有效枚举值,返回包含有效值的数组。

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    GetAllValidNames() 用于遍历数列范围内各个整数,找出全部有效枚举值的名字,返回包含这些名字的数组。

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    Enum2String() 用于从编译期生成的数组中遍历寻找枚举值等于参数的枚举值名字,如果枚举值无效或者超出范围就范围对应的整数字符串。

    总结

    • magic_enum 是个很不错的库,但他相对于我的需求来说显得太大了
    • 根据自己的需求改造开源库,一方面巩固了知识,另一方面也更适合自己的要求
    • constexpr 这个东东可以在编译期求值,后面可以多花点时间研究一下

    ==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

    原始财富的积累的真的是太难了,那些趁着各种东风各种红利的人们是幸运的,运气也是人生的一部分,而大部分没有这运气的人们想要积累财富,必然要付出十倍甚至上百倍的努力,这些不可选择也无需抱怨,只要踏踏实实往前走就好了~

  • 相关阅读:
    数据库、数据中台、数据仓库、数据湖区别
    Android 系统中适配OAID获取
    瑞芯微RK3568:Debian系统如何安装Docker
    Gin框架入门实战系列教程之Gin环境搭建 Gin程序的热加载 Gin路由 GET POST PUT DELETE
    JVM内存结构
    基于Springboot的旅游网管理系统设计与实现(有报告)。Javaee项目,springboot项目。
    C++异步:libunifex中的concepts详解!
    【源码+课程】Java精选课程_Java基础课程_名师讲解,从入门到精通,只需这一套课程_Java300集_附Java最全学习路线图和就业分析_持续更新中
    LabVIEW利用纳米结构干电极控制神经肌肉活动
    论文阅读-A General Language for Modeling Social Media Account Behavior
  • 原文地址:https://blog.csdn.net/shihengzhen101/article/details/126214355