【问题标题】:C++: Simplifiying a #defineC++:简化#define
【发布时间】:2021-08-09 09:54:21
【问题描述】:

我有一个#define 生成一个枚举类和一个对应的输出操作符生成的枚举类。(见下文)

#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)

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

  • 每个枚举都需要一个初始化值
  • 固定为 7 个枚举值

对于更通用的用法,我有两个问题。特别是第二个会极大地增加可用性。

这里有我的问题:

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

我正在等待您的 cmets 对我的代码提出改进建议。
雷纳

【问题讨论】:

  • 你在写 C++,那你为什么不用模板呢?
  • @Dai 使用模板将枚举转换为字符串相当晦涩。
  • 可以用 X 宏来完成
  • @Dai 我对模板不是很熟悉。我不知道如何用模板实现这一点,也不知道如何用模板解决我的问题。如果可以,请告诉我们如何操作!

标签: c++ variadic define-syntax


【解决方案1】:

比较接近你现在拥有的东西,你可以利用 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

【讨论】:

  • 不错的解决方案。当我理解您的评论时,无法为枚举定义初始值。但这对我来说很重要,因为我的一些列举有差距。
  • 啊,对 - 我以为你根本不想提供值。您可以调整上述内容以传递一系列日期名称/值元组而不是一系列日期名称,然后使用BOOST_PP_TUPLE_ELEM 再次将任一部分拉出......但请注意,这些东西往往会很快变成意大利面条.
  • 我成功了。替换两行:#define ENUM_FIELD(I,_,F) BOOST_PP_TUPLE_ELEM(0,F)=BOOST_PP_TUPLE_ELEM(1,F), #define ENUM_OUTPUT_CASE(I,N,F) case N::BOOST_PP_TUPLE_ELEM(0,F): os
  • 这两种解决方案可以结合使用吗?就像元组中的参数数量比旧解决方案为 1 一样,如果元组中的参数数量为 2 而不是新解决方案。对于 ENUM_FIELD 和 ENUM_OUTPUT_CASE
  • 是的,这应该是可能的。您可以询问 BOOST_PP_TUPLE_SIZE,然后根据它在宏之间进行选择。
【解决方案2】:

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

  • 用于打开/关闭输出枚举值的操纵器
    (在枚举后面的括号中)
  • 输出非法值
    (不应该发生:查看可能发生的代码)

这是我的完整解决方案:

#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)

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多