在不破坏现有代码的情况下添加新的枚举

时间:2020-10-16 18:20:48

标签: c++ clang

我有一个带有一个未定义和两个用户值的枚举:

class enum E
{
    UNDEFINED,
    VALUE1,
    VALUE2
};

我想添加VALUE3,但我担心其中有很多代码:

assert(val != E::UNDEFINED);
if(val == E::VALUE1)
{
}
else
{
    // Without an assert this wrongly assumes E::VALUE2
}

和:

something = (val == E::VALUE1) ? a : b; // last part assumes E::VALUE2

我喜欢编译器警告switch语句不处理所有枚举,并想知道是否有类似的东西可以显示上述所有实例?

我担心我不会找到并更新上述所有实例。

编译器是Clang

1 个答案:

答案 0 :(得分:3)

枚举不限于您给其命名的值。来自cppreference(格式是我的):

枚举是一种独特的类型,其值被限制在一个值的范围内(有关详细信息,请参见下文)

其中可能包含几个明确命名的常量(“枚举数”)。常量的值是称为枚举的基础类型的整数类型的值。

“下面的细节”解释了如何确定枚举基础类型。在此范围之外,我们(通常)仅给某些值命名,例如:

enum foo {A,B,C};

int main() {
    foo x = static_cast<foo>(42);
}

此代码完全正确。 x的基础值为42。该值没有名称,但这并不重要……除非您假设确实如此。

该代码对此做出了错误的假设:

assert(val != E::UNDEFINED);
if(val == E::VALUE1)
{
}
else
{
    // Without an assert this wrongly assumes E::VALUE2
}

此代码需要固定(与是否向枚举添加新的命名常量无关)。


现在要进行更严肃的审判以回答问题...


if-else链未涵盖所有枚举值时,将无法获得警告。您可以做的是将对枚举的所有if-else使用都变成错误。考虑一下这里实际发生的情况:

if (x == E::VALUE1) do_something();

switch(x) {
    case E::VALUE1 : return 1;
}

在if语句中,我们称为operator==(foo,foo);,其返回值要么是bool,要么隐式转换为1。使用开关,则不需要任何操作。我们可以利用它来将if-else枚举的用法转换为错误。忍受我,我将分两个步骤进行说明。首先让我们为if( x == E::VALUE1)创建一个编译器错误:

class helper { 
    operator bool(){ return false;} 
};

helper operator==(E,E){ 
    return {}; 
}

现在if (x == E::VALUE1)呼叫helper operator==(E,E),就可以了。然后将结果转换为bool,但由于转换为private而失败。在开关中使用枚举仍然可以,您可以依靠编译器错误/警告。基本思想是使某些东西只有在被调用时(在错误/正确的上下文中)才能编译失败。 (Live Demo)。

缺点是operator==的所有其他使用也已损坏。我们可以通过修改帮助程序和呼叫站点来解决它们:

#include <type_traits>

enum E {VALUE1};

struct helper { 
    bool value; 
private:
    operator bool(){ return false;} 
};

helper operator==(E a,E b){ 
    return {
        static_cast<std::underlying_type_t<E>>(a) == static_cast<std::underlying_type_t<E>>(b)
    }; 
}

int main() {
    E x{VALUE1};
    //if ( x== E::VALUE1); // ERROR
    bool is_same = (x == E::VALUE1).value;

    switch(x) {
        case E::VALUE1 : return 1;
    }
}

是的,必须编写.value是一个很大的不便,但是通过这种方式,您可以将if s中对枚举的所有使用都变成错误,而其他所有内容仍然可以编译。另外请注意,您必须确保涵盖所有您想抓的情况(例如!=<等)。