C++ 检测多个枚举项何时映射到同一值

C++ 检测多个枚举项何时映射到同一值,c++,c,enums,duplicates,C++,C,Enums,Duplicates,是否有编译时方法来检测/防止C/C++枚举中的重复值 问题是有多个项被初始化为显式值 背景: 我继承了一些C代码,如下所示: #define BASE1_VAL (5) #define BASE2_VAL (7) typedef enum { MsgFoo1A = BASE1_VAL, // 5 MsgFoo1B, // 6 MsgFoo1C, // 7 MsgFoo1D,

是否有编译时方法来检测/防止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;
static_assert( is_distinct< E, A, B >::value, "duplicate values in E detected" );
问题在于,随着代码的增长&随着开发人员向MsgFoo1x组添加更多消息,它最终会溢出BASE2_VAL


<>这个代码最终会被迁移到C++,所以如果有一个C++唯一的解决方案模板魔术,那就行了——但是一个C和C++的解决方案更好。

< P>我相信没有一种方法可以用语言本身来检测这个问题,考虑到有可能的情况下你希望两个枚举值相同。但是,您可以始终确保所有显式设置的项都位于列表的顶部:

typedef enum
{
  MsgFoo1A = BASE1_VAL,       // 5
  MsgFoo2A = BASE2_VAL,       // 7
  MsgFoo1B,                   // 8
  MsgFoo1C,                   // 9
  MsgFoo1D,                   // 10
  MsgFoo1E,                   // 11
  MsgFoo2B                    // 12
} FOO;
只要指定的值位于顶部,就不可能发生冲突,除非由于某种原因宏扩展为相同的值

通常,通过为每个MsgFooX组提供固定数量的位,并确保每个组不会溢出其分配的位数,可以克服此问题。位数解决方案很好,因为它允许按位测试来确定某些内容属于哪个消息组。但是没有内置的语言功能可以做到这一点,因为一个枚举有两个相同值的合法情况:

typedef enum
{
    gray = 4, //Gr[ae]y should be the same
    grey = 4,
    color = 5, //Also makes sense in some cases
    couleur = 5
} FOO;

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

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;
现在我们需要一种方法来确保MARKER_1_在编译时不使用 负大小数组 声明大小为负的数组是错误的。这看起来有点难看,但很管用

extern int IGNORE_ENUM_CHECK[MARKER_1_DONT_USE > BASE2_VAL ? -1 : 1];
如果MARKER_1_DONT_USE大于BASE_2_VAL,几乎所有编写的编译器都会生成一个错误。GCC指出:

test.c:16: error: size of array ‘IGNORE_ENUM_CHECK’ is negative
静态断言 如果编译器支持C11,则可以使用_Static_assert。对C11的支持并不普遍,但是编译器无论如何都支持OsStistaSyrt,特别是因为C++中的相应特性得到广泛支持。
_Static_assert(MARKER_1_DONT_USE < BASE2_VAL, "Enum values overlap.");
GCC给出了以下消息:

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

您可以推出一个更健壮的解决方案,使用来定义枚举—时间是否值得是另一回事

如果你正移动到C++,那么建议的升压.EnUM可能通过.< /P>适合你。


另一种方法可能是使用类似或更方便的方法来识别手动检查的候选对象。

我不知道有任何东西会自动检查所有枚举成员,但如果您希望检查对初始值设定项或它们所依赖的宏的未来更改是否不会导致冲突:

switch (0) {
    case MsgFoo1A: break;
    case MsgFoo1B: break;
    case MsgFoo1C: break;
    case MsgFoo1D: break;
    case MsgFoo1E: break;
    case MsgFoo2A: break;
    case MsgFoo2B: break;
}

如果重复使用任何整数值,将导致编译器错误,大多数编译器甚至会告诉您数值是什么值是一个问题。

我在您的要求中没有看到漂亮的东西,因此我提交了使用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)  \
        }                                                                 \
    }                                                                     \
}
枚举数值是可选的;此代码生成的枚举等效于:

enum DayOfWeek
{
    Monday = 1,
    Tuesday = 2,
    Wednesday,
    Thursday = 4
};
它还生成包含switch语句的健全性检查函数,如中所述。如果更改枚举声明,使其具有非唯一枚举数值,例如

DEFINE_UNIQUE_ENUM(DayOfWeek, ((Monday)    (1))
                              ((Tuesday)   (2))
                              ((Wednesday)    )
                              ((Thursday)  (1)))
< >它不会编译VisualC++报告预期错误C2196:已经使用的CASE值“1”。
也要感谢Matthieu M.让我对Boost预处理器库感兴趣。

虽然我们没有完全反射,但如果您可以重新列出枚举值,就可以解决这个问题

在某处声明:

enum E { A = 0, B = 0 };
在其他地方,我们建造了这种机器:

template<typename S, S s0, S... s>
struct first_not_same_as_rest : std::true_type {};
template<typename S, S s0, S s1, S... s>
struct first_not_same_as_rest : std::integral_constant< bool,
  (s0 != s1) && first_not_same_as_rest< S, s0, s... >::value
> {};


template<typename S, S... s>
struct is_distinct : std::true_type {};

template<typename S, S s0, S... s>
struct is_distinct : std::integral_constant< bool,
  std::is_distinct<S, s...>::value &&
  first_not_same_as_rest< S, s0, s... >::value
> {};
一旦您拥有需要C++11的机器,我们可以执行以下操作:

#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;
static_assert( is_distinct< E, A, B >::value, "duplicate values in E detected" );
在编译时,我们将确保没有两个元素是相等的

这需要编译器在编译时进行递归深度和^2工作,因此对于非常大的枚举,这可能会导致问题。通过首先对元素列表进行排序,可以完成具有更大常量因子的Olgn深度和On lgn工作,但这需要做更多的工作


使用针对C++1y-C++17的enum反射代码,这将是可行的,无需重新列出元素。

我不完全喜欢这里已经发布的任何答案,但他们给了我一些想法。关键技术是依赖于Ben Voight使用switch语句的答案。如果一个开关中的多个案例共享同一个数字,您将得到一个编译错误

最有用的是,我自己和可能的原始海报,这不需要 Y+C++特性 为了澄清问题,我用了aaronps的答案

首先,在某个地方的某个标题中定义:

#define DEFINE_ENUM_VALUE(name, value)      name=value,
#define CHECK_ENUM_VALUE(name, value)       case name:
#define DEFINE_ENUM(enum_name, enum_values) \
    typedef enum { enum_values(DEFINE_ENUM_VALUE) } enum_name;
#define CHECK_ENUM(enum_name, enum_values) \
    void enum_name ## _test (void) { switch(0) { enum_values(CHECK_ENUM_VALUE); } }
现在,无论何时需要枚举:

#define COLOR_VALUES(GEN) \
    GEN(Red, 1) \
    GEN(Green, 2) \
    GEN(Blue, 2)
DEFINE_ENUM(Color, COLOR_VALUES)
CHECK_ENUM(Color, COLOR_VALUES)
最后,实际进行枚举需要这些行:

#define COLOR_VALUES(GEN) \
    GEN(Red, 1) \
    GEN(Green, 2) \
    GEN(Blue, 2)
DEFINE_ENUM(Color, COLOR_VALUES)
CHECK_ENUM(Color, COLOR_VALUES)
DEFINE_ENUM使枚举数据类型本身。CHECK_ENUM生成一个打开所有枚举值的测试函数。如果有重复项,编译器在编译CHECK_ENUM时将崩溃。

这里有一个解决方案,使用而不使用Boost。首先定义X宏及其辅助宏。我使用可移植的方法为X宏创建2个重载,以便您可以使用或不使用显式值定义枚举。如果您使用的是GCC或Clang,那么它可以缩短

#define COUNT_X_ARGS_IMPL2(_1, _2, count, ...) count
#define COUNT_X_ARGS_IMPL(args) COUNT_X_ARGS_IMPL2 args
#define COUNT_X_ARGS(...) COUNT_X_ARGS_IMPL((__VA_ARGS__, 2, 1, 0))

/* Pick the right X macro to invoke. */
#define X_CHOOSE_HELPER2(count) X##count
#define X_CHOOSE_HELPER1(count) X_CHOOSE_HELPER2(count)
#define X_CHOOSE_HELPER(count)  X_CHOOSE_HELPER1(count)

/* The actual macro. */
#define X_GLUE(x, y) x y
#define X(...) X_GLUE(X_CHOOSE_HELPER(COUNT_X_ARGS(__VA_ARGS__)), (__VA_ARGS__))
然后定义宏并检查它

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

// Enum values
#define MY_ENUM             \
X(MsgFoo1A, BASE1_VAL)      \
X(MsgFoo1B)                 \
X(MsgFoo1C)                 \
X(MsgFoo1D)                 \
X(MsgFoo1E)                 \
X(MsgFoo2A, BASE2_VAL)      \
X(MsgFoo2B)

// Define the enum
#define X1(enum_name)               enum_name,
#define X2(enum_name, enum_value)   enum_name = enum_value,
enum foo
{
    MY_ENUM
};
#undef X1
#undef X2

// Check duplicates
#define X1(enum_name)               case enum_name: break;
#define X2(enum_name, enum_value)   case enum_name: break;
static void check_enum_duplicate()
{
    switch(0)
    {
        MY_ENUM
    }
}
#undef X1
#undef X2
使用它

int main()
{
// Do something with the whole enum
#define X1(enum_name)               printf("%s = %d\n", #enum_name, enum_name);
#define X2(enum_name, enum_value)   printf("%s = %d\n", #enum_name, enum_value);
    // Print the whole enum
    MY_ENUM
#undef X1
#undef X2
}

它不使用预处理器,但它是一个邪恶的黑客。尽管如此,这里所有有用的答案都接受了这个,因为它将在C中工作,C++中的很多FUD在C++中仍然被克服,并且它似乎最有可能被该组理解和跟随。我没有在示例中显示它,但是在枚举中有多个初始化项-现在大概有15个。每次添加新范围/组时,都需要添加另一个检查数组。任何有脉搏的人都应该看到这个模式并为新组添加一个新的检查数组。我希望我能提高投票率,因为我几乎可以提高任何提到boost的东西,但我以前没有使用过这个特定的库,所以不能:你有没有一个例子来演示它如何解决OP的问题?@Billy:我基本上只知道boost.PP可以做什么-我我还没来得及认真调查。不过,建议的Boost.Enum应该是一个很好的开始,使用它的示例生成枚举定义和适当的编译时检查应该不会太难。这让我意识到我使用Boost的力度不够,或者至少离它的功能还差得远-谢谢。看起来很棒。也许可以将检查移动到一个详细名称空间中,以避免它碍事。编辑:@GMan:既然我这么做了。。。Boost.Preprocessor库不支持C语言吗?我想会的。如果是这样的话,也许不使用名称空间会更好,这样它对两种语言都适用。嗯。。。早上我会考虑的。@James:嗯,说得好。我很确定Boost.PP是唯一一个在C语言中工作的Boost库。在C语言中,一个detail_uu前缀就足够了。您甚至可以使用uucplusplus进行选择,因此相同的头在这两个方面都起作用。很明显,生成不同的源。Woo great:很遗憾,你找不到更好的方法来定义值,而不是在序列中嵌入一个序列,我相信有一种方法可以用一个简单的逗号来实现,但我担心这需要在没有Boost PP的帮助下进行,我还没有勇气投入其中:至于切换,我可以建议您编写类似于toString方法的东西吗?我们毕竟可以在实际工作中使用该方法,而不是作为一个纯粹的编译器守卫:/me对自己的回答感到非常自豪cited@Matthieu,使用=定义初始值。对不起,我应该提到枚举应保持连续顺序,也就是说,MsgFoo1B应该与MsgFoo1A+1相同-我的错。编译器没有崩溃,只是抛出了一个错误。如果它崩溃了,那么肯定是编译器错误。另外,您的DEFINE_ENUM需要始终指定枚举值,这不是通常的情况,也不总是可能的。这非常聪明,您的示例非常有用。我必须在C和C++嵌入式系统之间进行移动,所以这种方法非常有用。非常感谢。