Непрерывное перечисление C ++ 11

17

Есть ли способ проверить в C ++ 11, является ли перечисление непрерывным ?

Полностью допустимо указывать значения enum, которых нет. Есть ли такая особенность, как черта типа в C ++ 14, C ++ 17 или, может быть, C ++ 20, чтобы проверить, является ли enum непрерывным? Это будет использоваться в static_assert.

Вот небольшой пример:

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
5
Нет возможности перечислять метки перечисления, поэтому это невозможно сделать из самой программы.
Какой-то программист чувак
1
Интересно. Я размышляю по шаблонному программированию по принципу, как вы можете получить компилятор для вычисления факториала. Вы начали бы с двух границ A и C, и функции шаблона проверяют через SFINAE наличие или нет всех значений между ними в enum. К сожалению, у меня есть дневная работа, поэтому я не могу попытаться выписать это, хотя я буду приветствовать ответ, основанный на этом подходе. Я уверен, что кто-то вроде @barry или @sehe может это сделать.
Вирсавия
1
@RoQuOTriX Как бы вы сопоставили значение с меткой? А как бы вы проверили порядок наклеек? И как это можно сделать во время компиляции (что необходимо для static_assert)? Даже если вы не можете принять «красивое решение», пожалуйста, напишите ответ, так как мне очень любопытно, как это можно сделать в общем виде.
Какой-то программист чувак
1
@ Someprogrammerdude то, что вы описали, является «красивым» или хорошим решением. Я имел в виду «простое» решение для проверки, которое вам придется переписывать для каждого перечисления и благословения, надеюсь, никто этого не делает
RoQuOTriX

Ответы:

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 ++», но, вероятно, настолько хорош, насколько мы сможем получить, пока у нас не появятся средства отражения в языке.

Н. Шеад
источник
Это действительно волшебство, но это лучше всего подходит для моей ситуации.
Барт
7

Это невозможно в чистом 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

Очевидно, что это специализировано для данного перечисления, но определение таких функций может быть автоматизировано с помощью препроцессора.

Андрей Семашев
источник
Если я правильно понимаю, это все равно потребует записи всех перечислимых значений в переключателе и списке для minmax. В настоящее время у меня есть несколько перечислений, так что это действительно возможно, но не предпочтительнее для моей ситуации.
Барт
1

Я хотел бы увидеть ответ на этот вопрос. Мне это тоже нужно.

К сожалению, я не думаю, что это возможно с использованием существующих утилит. Если вы хотите реализовать черту типа в этом, вам нужна поддержка вашего компилятора, поэтому написание шаблона для него не представляется возможным.

Я уже расширил перечисление специальным тегом, чтобы указать, что он является смежным, и сразу же дает вам конструктор класса enum c ++, как передать конкретное значение?

Кроме того, вы можете написать свою собственную черту:

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

Это должно быть специализированным всякий раз, когда вы определяете непрерывное перечисление, где вы хотите использовать это. К сожалению, это требует некоторого обслуживания и внимания, если перечисление изменяется.

JVApen
источник
1
Вы можете написать код проверки, который проверяет во время компиляции, если тип установлен правильно
RoQuOTriX
Да, в самом деле. Если у вас есть возможность написать это.
JVApen
1

Все перечисления непрерывны. 0 всегда разрешено; самое высокое допустимое значение - это самый высокий перечислитель, округленный до следующего 1<<N -1(все биты один), и все значения между ними также разрешены. ([dcl.enum] 9.7.1 / 5). Если определены отрицательные перечислители, то самое низкое допустимое значение аналогично определяется округлением в меньшую сторону перечислителя.

Перечисленные в enumвыражениях перечислители являются константными выражениями со значением в диапазоне и правильным типом, но вы можете определить дополнительные константы, enumкоторые имеют те же свойства:

constexpr enum class Types_Discontinuous = static_cast<Types_Discontinuous>(2)

MSalters
источник
2
Хотя вы правы, из OP ясно, что мы хотим знать это для определенных значений. (PS: голосование против не мое)
JVApen
1
@JVApen: Это точная проблема. «Определенные значения» не являются свойством самого типа enum. В стандарте четко указаны значения перечисления.
MSalters