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

时间:2015-03-27 11:47:59

标签: 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

1 个答案:

答案 0 :(得分:0)

使用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 - 它是目前唯一被迫延迟到运行时的库。