c++ - 使用 Boost PP 枚举到 std::string - 改进语法

标签 c++ boost enums boost-preprocessor

我已经开始使用 Boost 的 PP 库,目的是编写一些小工具来帮助将枚举转换为字符串。

我设法得到了一个解决方案,其中用宏定义的枚举也将生成一个互补的 ToString 函数。

我尝试过各种语法风格,但我无法让一种特定的安排发挥作用。

这就是我的。

    namespace Examples
    {
        // This will define an enum class with an underlying uint8_t type
        ENUM_W_STR
        (fruit, uint8_t,
            (apple)
            (banana)
        );

        // Like above but with a different construction syntax
        ENUM_W_STR_2
        (fruit2, uint8_t,
            apple,
            banana
        );

        // This variant allows values to be assigned
        ENUM_W_STR_VAL
        (fruit3, uint8_t,
            (apple)      (1)
            (banana)     (2)
            (orange)     (3)
        );
    }

但是,这就是我想要的。

    ENUM_W_STR_2
    (fruit2, uint8_t,
        apple = 10,
        banana = 20
    );

我在拆分“apple = 10”字符串时遇到了问题 在 ToString 中生成 switch 语句时。即,我可以构建 枚举而不是 ToString 函数。

这能实现吗?如果不使用 Boost PP,那么还有其他方法吗?

这是源代码(g++ 4.7.2,boost 1.50;clang 3.3 boost 1.57)

    #include <boost/preprocessor.hpp>
    #include <iostream>

    namespace EnumUtils{

    #define ENUMUTLS_STR_SWITCH_CASE(r, data, elem)\
        case data::elem : return BOOST_PP_STRINGIZE(elem);

    #define ENUMUTIL_EVEN(r, data,i, v)  BOOST_PP_IIF(BOOST_PP_MOD_D(r, i, 2),   ,v)
    #define ENUMUTIL_ODD( r, data,i, v)  BOOST_PP_IIF(BOOST_PP_MOD_D(r, i, 2),= v, )
    #define ENUMUTIL_ENUM_LINE(r, data, i, v)     ENUMUTIL_EVEN(r,data,i,v)    ENUMUTIL_ODD(r,data,i,v)    BOOST_PP_COMMA_IF(BOOST_PP_MOD_D(r,i,2))

    #define ENUMUTLS_STR_SWITCH_CASE_VAL_BASE(r, data, i, v)  BOOST_PP_IIF(BOOST_PP_MOD_D(r, i, 2),,ENUMUTLS_STR_SWITCH_CASE(r, data, v))
    #define ENUMUTLS_STR_SWITCH_CASE_VAL(     r, data, i, v)  ENUMUTLS_STR_SWITCH_CASE_VAL_BASE(r,data,i,v) 


    #define ENUM_W_STR(name, type, seq)\
        enum class name : type {\
            BOOST_PP_SEQ_ENUM(seq)\
        };\
        inline const char* ToString(name v)\
        {\
            switch (v)\
            {\
                BOOST_PP_SEQ_FOR_EACH(\
                        ENUMUTLS_STR_SWITCH_CASE,\
                        name,\
                        seq)\
                default: return "unknown " BOOST_PP_STRINGIZE(name);\
            }\
        }

    #define ENUM_W_STR_2(name, type, args...)\
        enum class name : type {\
            BOOST_PP_SEQ_ENUM(BOOST_PP_VARIADIC_TO_SEQ(args))\
        };\
        inline const char* ToString(name v)\
        {\
            switch (v)\
            {\
                BOOST_PP_SEQ_FOR_EACH(\
                        ENUMUTLS_STR_SWITCH_CASE,\
                        name,\
                        BOOST_PP_VARIADIC_TO_SEQ(args))\
                default: return "unknown " BOOST_PP_STRINGIZE(name);\
            }\
        }


    #define ENUM_W_STR_VAL(name, type, seq)\
        enum class name : type {\
            BOOST_PP_SEQ_FOR_EACH_I(\
                    ENUMUTIL_ENUM_LINE,\
                    name,\
                    seq)\
        };\
        inline const char* ToString(name v)\
        {\
            switch (v)\
            {\
                BOOST_PP_SEQ_FOR_EACH_I(\
                        ENUMUTLS_STR_SWITCH_CASE_VAL,\
                        name,\
                        seq)\
                default: return "unknown " BOOST_PP_STRINGIZE(name) ;\
            }\
        };
    }

    namespace Examples 
    {
      ENUM_W_STR 
      (fruit, uint8_t,
        (apple)
        (banana)
      );

      ENUM_W_STR_2
      (fruit2, uint8_t,
        apple,
        banana
      );

      ENUM_W_STR_VAL
      (fruit3, uint8_t,
        (apple)      (1)
        (banana)     (2)
        (orange)     (3)
     );
    }

    int main ()
    {
        std::cout << Examples::ToString(Examples::fruit::apple)   << std::endl;
        std::cout << Examples::ToString(Examples::fruit::banana)  << std::endl;
        std::cout << Examples::ToString(Examples::fruit2::apple)  << std::endl;
        std::cout << Examples::ToString(Examples::fruit2::banana) << std::endl;
        std::cout << Examples::ToString(Examples::fruit3::apple)  << std::endl;
        std::cout << Examples::ToString(Examples::fruit3::banana) << std::endl;
        return 0;
    }

输出:

apple
banana
apple
banana
apple
banana

最佳答案

使用 C++11 constexpr 函数和宏可以实现这种修剪。以下是该方法的概述:

#include <iostream>

constexpr const char    terminators[] = " \t\r\n=";

// Note that this count includes the null terminator, which is deliberate.
constexpr size_t        terminator_count = sizeof(terminators);

// Checks if a character is a terminator at compile time.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= terminator_count ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

// Computes enum constant length at compile time.
constexpr size_t constant_length(const char *s, size_t index = 0)
{
    return is_terminator(s[index]) ? index : constant_length(s, index + 1);
}

// Evaluates to characters from "from" up to length of "from".
constexpr char select(const char *from, size_t from_length, size_t index)
{
    return index >= from_length ? '\0' : from[index];
}



constexpr const char    *foo = "apple = 10";
constexpr size_t        foo_length = constant_length(foo);

// You should use a macro to generate this, either from Boost, or your own.
// Ideally, the user would be able to change the maximum string length.
constexpr const char    trimmed[] =
    {select(foo, foo_length, 0), select(foo, foo_length, 1),
     select(foo, foo_length, 2), select(foo, foo_length, 3),
     select(foo, foo_length, 4), select(foo, foo_length, 5),
     select(foo, foo_length, 6), select(foo, foo_length, 7)};

int main()
{
    std::cout << foo << std::endl;
    std::cout << trimmed << std::endl;

    return 0;
}

输出是

apple = 10
apple

常量名的最大长度是有限制的,从trimmed的定义可以看出。正如它所说,最大长度可能应该由用户在遇到问题时可以定义的可选宏来控制。当然,默认值应该大于你在我上面的草图中看到的 8:)

如果您有兴趣,我刚刚发布了一个枚举库,其声明语法与您在问题中描述的几乎相同(尽管内部结构完全不同):http://aantron.github.io/better-enums

我将很快将这种编译时字符串修剪添加到库中,以使其 to_string 方法 constexpr——这是目前库中唯一的东西被迫延迟到运行时。

关于c++ - 使用 Boost PP 枚举到 std::string - 改进语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29299991/

相关文章:

c++ - 使用 boost 的源特定多播

spring - Spring ConversionFailedException 错误 HTTP 状态的自定义

c# - 使用初始化器循环遍历枚举会产生意想不到的结果

c# - 如何在 C# 中从 System.Array 转换为 object[]

C++ dwarf 标签编号格式

c++ - 无法通过 std::ref() 使用 auto& 参数调用 std::invoke()

c++ - boost::interprocess_mutex 与进程本地 boost::mutex

c++ - 使用 Boost ASIO 进行 Boost 异常处理

c++ - 在 VS 调试器中查看映射条目

c++ - 如何在 C++ 中初始化私有(private)静态成员?