C++:简化#define

C++: Simplifiying a #define

我有一个 #define 生成一个枚举 class 和一个对应的输出运算符生成的枚举 class。(见下文)

#define ENUM(N, T, N1, V1, N2, V2, N3, V3, N4, V4, N5, V5, N6, V6, N7, V7)\
    enum class N : T {\
        N1 = V1,\
        N2 = V2,\
        N3 = V3,\
        N4 = V4,\
        N5 = V5,\
        N6 = V6,\
        N7 = V7,\
    };\
    std::ostream &operator <<(std::ostream &os, const N val);   /* declare function to avoid compiler warning */\
    std::ostream &operator <<(std::ostream &os, const N val) {\
        switch (val) {\
        case N::N1:\
            os << #N1;\
            break;\
        case N::N2:\
            os << #N2;\
            break;\
        case N::N3:\
            os << #N3;\
            break;\
        case N::N4:\
            os << #N4;\
            break;\
        case N::N5:\
            os << #N5;\
            break;\
        case N::N6:\
            os << #N6;\
            break;\
        case N::N7:\
            os << #N7;\
            break;\
        }\
        if (sizeof(T) == 1) {\
            os << '(' << static_cast<int>(val) << ')';\
        } else {\
            os << '(' << static_cast<T>(val) << ')';\
        }\
        return os;\
    }

在这个例子中可以这样使用:

#include <cstdlib>
#include <iostream>
#include <ostream>

ENUM(Weekdays, unsigned char, Monday, 10, Tuesday, 12, Wednesday, 14, Thursday, 16, Friday, 18, Saterday, 100, Sunday, 101)

int main(const int /*argc*/, const char *const /*argv*/[]) {
    Weekdays    test    = Weekdays::Monday;

    std::cout << test << std::endl;
    std::cout << Weekdays::Tuesday << std::endl;
    std::cout << Weekdays::Sunday << std::endl;

    return EXIT_SUCCESS;
}

此处生成的输出:

Monday(10)
Tuesday(12)
Sunday(101)

我的解决方案有一些限制:

对于更广泛的用法,我有两个问题。尤其是第二个会大大提高可用性。

这里有我的问题:

  1. 如何避免为每个枚举值定义一个初始化值?
    (就像在真实的枚举中一样)
  2. 有什么想法可以概括 #define 以使用任意数量的值吗?

我正在等待您对我的代码提出意见和改进建议。
莱纳

相对接近你现在拥有的东西,你可以利用 Boost.Preprocessor 中的 BOOST_PP_SEQ_FOR_EACH 宏,它看起来像这样:

#include <boost/preprocessor.hpp>

#define ENUM_FIELD(I,_,F) F,
#define ENUM_OUTPUT_CASE(I,N,F) case N::F: os << BOOST_PP_STRINGIZE(F); break;

#define ENUM(N, T, ARGS) \
enum class N : T {\
BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
};\
std::ostream &operator <<(std::ostream &os, const N val) {\
    switch (val) {\
    BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
    }\
    \
    os << '(' << static_cast<int>(val) << ')';\
    return os;\
}

ENUM(Weekdays, unsigned char, (Monday)(Tuesday)(Wednesday)(Thursday)(Friday)(Saturday)(Sunday))

这消除了提供值的重复和可能性。整个过程更短了,可以说是以降低可读性并可能更难调试为代价的——我不会权衡使用这些宏的利弊。

请注意,我已经更改了将参数传递给 ENUM 宏的方式:现在这是一个 Boost.Preprocessor 序列 。您最多应该能够传递 256 个元素;有关更多信息和更多适用于序列的宏,请参阅 documentation

如果你不需要知道这个编译时间,你可以使用像 Protobuf 这样的库来定义你的枚举。C++ 中的 Protobuf 支持枚举描述符,它可以用作反射的一种形式。这两篇文章描述了使用 Protobuf 的可能解决方案 ( and )。

编辑: 我忘了还有另一个库可能对你有用,如果你需要它编译时间。它称为 Frozen 并提供编译时映射。您也许能够生成一些定义映射的代码,并使用它将枚举值转换为字符串。

我让它为我工作。添加了一些特殊功能:

  • 操纵器切换on/off输出枚举的值
    (在枚举后面的括号中)
  • 输出非法值
    (不应该发生:查看可能发生的代码)

这是我的完整解决方案:

#include <cstdlib>
#include <iostream>
#include <ostream>

#include <boost/preprocessor.hpp>
#include <boost/preprocessor/tuple/elem.hpp>

class EnumShowValue {
private:
    static bool showValueFlag;
public:
    explicit EnumShowValue(const bool flag) { EnumShowValue::showValueFlag  = flag; }

    static bool showValue() { return EnumShowValue::showValueFlag; }
};
bool    EnumShowValue::showValueFlag    = false;

inline std::ostream &operator <<(std::ostream &os, const EnumShowValue &) { return os; }

#define ENUM_FIELD(I,_,F)\
    BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(F),2),\
                    BOOST_PP_TUPLE_ELEM(0,F)=BOOST_PP_TUPLE_ELEM(1,F),\
                    BOOST_PP_TUPLE_ELEM(0,F)),

#define ENUM_OUTPUT_CASE(I,N,F)\
    case N::BOOST_PP_TUPLE_ELEM(0,F):\
        os << BOOST_PP_STRINGIZE(BOOST_PP_TUPLE_ELEM(0,F));\
        break;

#define ENUM(N, T, ARGS) \
enum class N : T {\
BOOST_PP_SEQ_FOR_EACH(ENUM_FIELD,N,ARGS)\
};\
std::ostream &operator <<(std::ostream &os, const N val);\
std::ostream &operator <<(std::ostream &os, const N val) {\
    switch (val) {\
    BOOST_PP_SEQ_FOR_EACH(ENUM_OUTPUT_CASE,N,ARGS)\
    default:\
        os << "illegal value: " << BOOST_PP_STRINGIZE(N);\
        if (!EnumShowValue::showValue()) {\
            os << '(';\
            if (sizeof(T) == 1) {\
                os << static_cast<int>(val);\
            } else {\
                os << static_cast<T>(val);\
            }\
            os << ')';\
        }\
    }\
    if (EnumShowValue::showValue()) {\
                    os << '(';\
                    if (sizeof(T) == 1) {\
                        os << static_cast<int>(val);\
                    } else {\
                        os << static_cast<T>(val);\
                    }\
                    os << ')';\
    }\
    return os;\
}

ENUM(Weekdays, unsigned char, ((Monday, 101))((Tuesday))((Wednesday))((Thursday))((Friday))((Saturday, 200))((Sunday)))

int main(const int /*argc*/, const char *const /*argv*/[]) {

    std::cout << Weekdays::Monday << std::endl;
    std::cout << Weekdays::Tuesday << std::endl;
    std::cout << Weekdays::Wednesday << std::endl;
    std::cout << Weekdays::Thursday << std::endl;
    std::cout << Weekdays::Friday << std::endl;
    std::cout << Weekdays::Saturday << std::endl;
    std::cout << Weekdays::Sunday << std::endl;
    std::cout << Weekdays(99) << std::endl;

    std::cout << EnumShowValue(true);
    std::cout << Weekdays::Monday << std::endl;
    std::cout << Weekdays::Tuesday << std::endl;
    std::cout << Weekdays::Wednesday << std::endl;
    std::cout << Weekdays::Thursday << std::endl;
    std::cout << Weekdays::Friday << std::endl;
    std::cout << Weekdays::Saturday << std::endl;
    std::cout << EnumShowValue(false);
    std::cout << Weekdays::Sunday << std::endl;
    std::cout << Weekdays(-1) << std::endl;

    return EXIT_SUCCESS;
}

和相应的输出:

Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
illegal value: Weekdays(99)
Monday(101)
Tuesday(102)
Wednesday(103)
Thursday(104)
Friday(105)
Saturday(200)
Sunday
illegal value: Weekdays(255)