C/C++枚举:检测多个项目映射到相同值的时间

Dan*_*Dan 22 c c++ enums

是否有编译时方法来检测/防止C/C++枚举中的重复值?

问题在于有多个项目被初始化为显式值.

背景:

我继承了一些C代码,如下所示:

#define BASE1_VAL    (5)
#define BASE2_VAL    (7)

typedef enum
{
  MsgFoo1A = BASE1_VAL,       // 5
  MsgFoo1B,                   // 6
  MsgFoo1C,                   // 7
  MsgFoo1D,                   // 8
  MsgFoo1E,                   // 9
  MsgFoo2A = BASE2_VAL,       // Uh oh!  7 again...
  MsgFoo2B                    // Uh oh!  8 again...
} FOO;
Run Code Online (Sandbox Code Playgroud)

问题是随着代码的增长和开发人员向MsgFoo1x组中添加更多消息,最终它会超出BASE2_VAL.

这段代码最终会迁移到C++,所以如果只有一个C++解决方案(模板魔术?),那没关系 - 但是使用C和C++的解决方案更好.

Die*_*Epp 14

有几种方法可以检查这个编译时间,但它们可能并不总是适合您.首先在MsgFoo2A之前插入一个"marker"枚举值.

typedef enum
{
    MsgFoo1A = BASE1_VAL,
    MsgFoo1B,
    MsgFoo1C,
    MsgFoo1D,
    MsgFoo1E,
    MARKER_1_DONT_USE, /* Don't use this value, but leave it here.  */
    MsgFoo2A = BASE2_VAL,
    MsgFoo2B
} FOO;
Run Code Online (Sandbox Code Playgroud)

现在我们需要一种方法来确保MARKER_1_DONT_USE < BASE2_VAL在编译时.有两种常见的技术.

负大小的数组

声明具有负大小的数组是错误的.这看起来有点难看,但它确实有效.

extern int IGNORE_ENUM_CHECK[MARKER_1_DONT_USE > BASE2_VAL ? -1 : 1];
Run Code Online (Sandbox Code Playgroud)

如果MARKER_1_DONT_USE大于BASE_2_VAL,几乎每个编写的编译器都会生成错误.海湾合作委员会吐出:

test.c:16: error: size of array ‘IGNORE_ENUM_CHECK’ is negative
Run Code Online (Sandbox Code Playgroud)

静态断言

如果您的编译器支持C11,您可以使用_Static_assert.对C11的支持并不普遍,但您的编译器可能_Static_assert仍然支持,特别是因为C++中的相应功能得到了广泛支持.

_Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap.");
Run Code Online (Sandbox Code Playgroud)

GCC吐出以下消息:

test.c:16:1: error: static assertion failed: "Enum values overlap."
 _Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap.");
 ^
Run Code Online (Sandbox Code Playgroud)

  • 它不使用预处理器,但它仍然是一个邪恶的黑客. (10认同)

Jam*_*lis 7

我没有在您的要求中看到"漂亮",所以我提交了使用Boost预处理器库实现的解决方案.

作为一个前期免责声明,我没有使用Boost.Preprocessor很多,我只用这里介绍的测试用例测试了这个,所以可能有bug,并且可能有一个更容易,更清晰的方法来做到这一点.我当然欢迎评论,更正,建议,侮辱等.

开始了:

#include <boost/preprocessor.hpp>

#define EXPAND_ENUM_VALUE(r, data, i, elem)                          \
    BOOST_PP_SEQ_ELEM(0, elem)                                       \
    BOOST_PP_IIF(                                                    \
        BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),                  \
        = BOOST_PP_SEQ_ELEM(1, elem),                                \
        BOOST_PP_EMPTY())                                            \
    BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(data, BOOST_PP_ADD(i, 1)))

#define ADD_CASE_FOR_ENUM_VALUE(r, data, elem) \
    case BOOST_PP_SEQ_ELEM(0, elem) : break;

#define DEFINE_UNIQUE_ENUM(name, values)                                  \
enum name                                                                 \
{                                                                         \
    BOOST_PP_SEQ_FOR_EACH_I(EXPAND_ENUM_VALUE,                            \
                            BOOST_PP_SEQ_SIZE(values), values)            \
};                                                                        \
                                                                          \
namespace detail                                                          \
{                                                                         \
    void UniqueEnumSanityCheck##name()                                    \
    {                                                                     \
        switch (name())                                                   \
        {                                                                 \
            BOOST_PP_SEQ_FOR_EACH(ADD_CASE_FOR_ENUM_VALUE, name, values)  \
        }                                                                 \
    }                                                                     \
}
Run Code Online (Sandbox Code Playgroud)

然后我们可以像这样使用它:

DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday)    (1))
                              ((Tuesday)   (2))
                              ((Wednesday)    )
                              ((Thursday)  (4)))
Run Code Online (Sandbox Code Playgroud)

枚举器值是可选的; 此代码生成一个等效于的枚举:

enum DayOfWeek
{
    Monday = 1,
    Tuesday = 2,
    Wednesday,
    Thursday = 4
};
Run Code Online (Sandbox Code Playgroud)

它还会生成一个完整性检查功能,其中包含Ben Voigt的答案中描述的switch语句.如果我们更改枚举声明,以便我们有非唯一的枚举值,例如,

DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday)    (1))
                              ((Tuesday)   (2))
                              ((Wednesday)    )
                              ((Thursday)  (1)))
Run Code Online (Sandbox Code Playgroud)

它不会编译(Visual C++报告预期的错误C2196:已使用案例值'1').

还要感谢Matthieu M.,他对另一个问题的回答让我对Boost预处理器库感兴趣.