持续枚举 C++11

17

在C++11中,有没有一种方法可以检查枚举类型是否为连续的

给一个枚举类型赋非连续值是完全有效的。在C++14、C++17或者C++20中,是否有像类型特性这样的功能来检查枚举类型是否连续?这可以用于静态断言。

以下是一个小例子:

enum class Types_Discontinuous {
  A = 10,
  B = 1,
  C = 100
};

enum class Types_Continuous {
  A = 0,
  B = 1,
  C = 2
};

static_assert(SOME_TEST<Types_Discontinuous>::value, "Enum should be continuous"); // Fails
static_assert(SOME_TEST<Types_Continuous>::value, "Enum should be continuous");    // Passes

1
这是指连续的,它具有升序,还是指从零开始,然后每个值+1? - RoQuOTriX
6
程序内部无法枚举列举标签,因此无法从程序内部进行枚举。 - Some programmer dude
1
有趣。我正在考虑模板编程,就像您可以让编译器计算阶乘一样。您将以两个边界A和C开始,然后模板函数通过SFINAE检查enum中是否存在所有这些值(否则)的存在与否。不幸的是我有一份工作,所以无法尝试写出来,尽管我会根据这种方法为答案投票。我相信像@barry或@sehe这样的人可以做到。 - Bathsheba
1
@RoQuOTriX 你如何将一个值与标签匹配?你如何检查标签的顺序?如何在编译时完成(这对于static_assert是必需的)?即使你不能提供一个“漂亮的解决方案”,也请写下答案,因为我非常好奇它如何以一种通用的方式完成。 - Some programmer dude
1
@Someprogrammerdude,你所描述的是“美丽”或好的解决方案。我所指的是“简单”的检查解决方案,你必须为每个枚举重新编写它,天佑吾人,希望没有人这样做。 - RoQuOTriX
显示剩余11条评论
4个回答

8

纯C++中无法实现此功能,因为没有办法枚举枚举值或发现值的数量、最小值和最大值。但你可以尝试使用编译器的帮助来实现接近你想要的功能。例如,在gcc中,如果switch语句未处理枚举的所有值,则可以强制发生编译错误。

enum class my_enum {
    A = 0,
    B = 1,
    C = 2
};

#pragma GCC diagnostic push
#if __GNUC__ < 5
#pragma GCC diagnostic error "-Wswitch"
#else
#pragma GCC diagnostic error "-Wswitch-enum"
#endif

constexpr bool is_my_enum_continuous(my_enum t = my_enum())
{
    // Check that we know all enum values. Effectively works as a static assert.
    switch (t)
    {
    // Intentionally no default case.
    // The compiler will give an error if not all enum values are listed below.
    case my_enum::A:
    case my_enum::B:
    case my_enum::C:
        break;
    }

    // Check that the enum is continuous
    auto [min, max] = std::minmax({my_enum::A, my_enum::B, my_enum::C});
    return static_cast< int >(min) == 0 && static_cast< int >(max) == 2;
}

#pragma GCC diagnostic pop

显然,这是针对特定枚举类型的专用功能,但是可以使用预处理器自动定义此类函数。

如果我理解正确的话,这仍然需要在switch和minmax列表中写出所有枚举值。目前我有多个枚举,所以确实是可能的,但对于我的情况并不理想。 - Bart
"无法枚举枚举值" 概括了 C 中枚举的问题... - Puck

7

对于许多enum,你可能可以使用Magic Enum库来解决这个问题。例如:

#include "magic_enum.hpp"

template <typename Enum>
constexpr bool is_continuous(Enum = Enum{}) {
    // make sure we're actually testing an enum
    if constexpr (!std::is_enum_v<Enum>)
        return false;
    else {
        // get a sorted list of values in the enum
        const auto values = magic_enum::enum_values<Enum>();
        if (std::size(values) == 0)
            return true;

        // for every value, either it's the same as the last one or it's one larger
        auto prev = values[0];
        for (auto x : values) {
            auto next = static_cast<Enum>(magic_enum::enum_integer(prev) + 1);
            if (x != prev && x != next)
                return false;
            else
                prev = x;
        }
        return true;
    }
}

请注意,正如库名称所暗示的,这确实是一种“魔法”——该库使用了许多特定于编译器的技巧。因此,它并不真正符合你对“纯C ++”的要求,但在我们拥有语言反射功能之前,这可能是我们能够得到的最好的选择。

2
所有的枚举值都是连续的。0 总是被允许的;最高允许值是最高枚举器向上舍入到下一个 1<<N -1(所有位都为一),中间的所有值也都被允许。如果定义了负枚举器,则最低允许值也是通过向下舍入最低枚举器来定义的。枚举器在 enum 中定义为具有范围和正确类型的常量表达式,但您可以在 enum 之外定义具有相同属性的其他常量:constexpr enum class Types_Discontinuous = static_cast<Types_Discontinuous>(2)

2
尽管你是正确的,但从 OP 中可以清楚地看出我们想要了解这些定义值。(PS:我没有投反对票) - JVApen
1
@JVApen:这正是问题所在。 "定义的值" 不是枚举类型本身的属性。标准明确规定了枚举的值是什么。 - MSalters

2

我很想看到这个问题的答案。我也一直需要它。

不幸的是,我认为使用现有的工具不可能实现这一点。如果您想在此上实现类型特性,则需要编译器的支持,因此编写模板似乎不可行。

我已经使用特定标记扩展了枚举,以指示它是连续的并立即给出大小:enum class constructor c++ , how to pass specific value?

或者,您可以编写自己的特性:

 template<T> struct IsContiguous : std::false_type {};

每当您定义一个连续的枚举并想要使用它时,就需要进行专门化。不幸的是,如果枚举发生更改,则需要进行一些维护和注意。


1
你可以编写一个代码检查器,在编译时检查类型是否设置正确。 - RoQuOTriX
是的,确实如此。如果你有写它的能力的话。 - JVApen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接