Ciągłe wyliczanie C ++ 11


17

Czy istnieje sposób sprawdzenia w C ++ 11, czy wyliczenie jest ciągłe ?

W pełni uzasadnione jest podawanie wartości wyliczeniowych, które nie są. Czy w C ++ 14, C ++ 17 jest jakaś cecha typu, a może C ++ 20, aby sprawdzić, czy wyliczanie jest ciągłe? Należy tego użyć w static_assert.

Oto mały przykład:

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
Oznacza to, że ma rosnącą kolejność lub oznacza, że ​​zaczyna się od zera, a następnie +1 dla każdej wartości?
RoQuOTriX

5
Nie ma sposobu na wyliczenie etykiet wyliczenia, więc nie można tego zrobić z poziomu samego programu.
Jakiś programista koleś

1
Ciekawy. Myślę o programowaniu szablonów o tym, jak uzyskać kompilator do obliczania silni. Zaczynasz od dwóch granic A i C, a funkcje szablonów sprawdzają za pośrednictwem SFINAE obecność lub w inny sposób wszystkich wartości między nimi w enum. Niestety mam dzienną pracę, więc nie mogę spróbować tego napisać, chociaż głosuję za odpowiedzią w oparciu o to podejście. Jestem prawie pewien, że ktoś taki jak @barry lub @sehe mógłby to zrobić.
Batszeba

1
@RoQuOTriX Jak dopasowałbyś wartość do etykiety? Jak sprawdziłbyś kolejność etykiet? Jak można to zrobić w czasie kompilacji (co jest potrzebne static_assert)? Nawet jeśli nie możesz stworzyć „pięknego rozwiązania”, napisz odpowiedź, ponieważ jestem bardzo ciekawy, jak można to zrobić w sposób ogólny.
Jakiś programista koleś

1
@ Someprogrammerdude to, co opisałeś, jest „pięknym” lub dobrym rozwiązaniem. Miałem na myśli „łatwe” rozwiązanie do sprawdzania, które musiałbyś przepisać na każde wyliczenie i
Boże

Odpowiedzi:


7

Przez kilka enumsekund możesz prawdopodobnie przebić się przez to za pomocą biblioteki Magic Enum . Na przykład:

#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;
    }
}

Zauważ, że jest to rzeczywiście, jak sugeruje nazwa biblioteki, „magia” - biblioteka działa na wielu hackach specyficznych dla kompilatora. W związku z tym tak naprawdę nie spełnia twoich wymagań dotyczących „czystego C ++”, ale prawdopodobnie jest tak dobry, jak to tylko możliwe, dopóki nie będziemy mieli możliwości refleksji w języku.


To rzeczywiście magia, ale najlepiej pasowałaby do mojej sytuacji.
Bart

7

Nie jest to możliwe w czystym C ++, ponieważ nie ma sposobu, aby wyliczyć wartości wyliczeniowe lub odkryć liczbę wartości oraz wartości minimalne i maksymalne. Ale możesz spróbować skorzystać z pomocy swojego kompilatora, aby zaimplementować coś blisko tego, co chcesz. Na przykład w gcc można wymusić błąd kompilacji, jeśli switchinstrukcja nie obsługuje wszystkich wartości wyliczenia:

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

Oczywiście jest to wyspecjalizowane dla danego wyliczenia, ale definiowanie takich funkcji można zautomatyzować za pomocą preprocesora.


Jeśli dobrze rozumiem, nadal wymagałoby to zapisania wszystkich wartości wyliczeniowych w przełączniku i liście dla minmax. Obecnie mam wiele wyliczeń, więc jest to rzeczywiście możliwe, ale nie preferowane w mojej sytuacji.
Bart

1

Chciałbym zobaczyć odpowiedź na to pytanie. Ja też tego potrzebowałem.

Niestety nie sądzę, że jest to możliwe przy użyciu istniejących narzędzi. Jeśli chcesz zaimplementować na nim cechę typu, potrzebujesz wsparcia ze strony kompilatora, więc napisanie szablonu dla niego nie wydaje się wykonalne.

Już rozszerzyłem wyliczenie o określony znacznik, aby wskazać, że jest ciągły i od razu daje ci rozmiar: konstruktor klasy enum c ++, jak przekazać określoną wartość?

Możesz też napisać własną cechę:

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

Musi to być wyspecjalizowane za każdym razem, gdy definiujesz ciągły wyliczenie, w którym chcesz go użyć. Niestety wymaga to konserwacji i uwagi, jeśli wyliczenie ulegnie zmianie.


1
Możesz napisać kontroler kodu, który sprawdza podczas kompilacji, jeśli typ jest ustawiony poprawnie
RoQuOTriX

W rzeczy samej. Jeśli potrafisz to napisać.
JVApen

1

Wszystkie wyliczenia są ciągłe. 0 jest zawsze dozwolone; najwyższa dozwolona wartość to najwyższy numerator zaokrąglony w górę do następnego 1<<N -1(wszystkie bity jeden), a wszystkie wartości pomiędzy nimi są również dozwolone. ([dcl.enum] 9.7.1 / 5). Jeśli zdefiniowano ujemne elementy wyliczające, najniższa dozwolona wartość jest podobnie zdefiniowana przez zaokrąglenie w dół najniższego elementu wyliczającego.

Liczniki zdefiniowane w enumwyrażeniach są stałymi wyrażeniami o wartości w zakresie i poprawnym typie, ale można zdefiniować dodatkowe stałe poza tymi, enumktóre mają te same właściwości:

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


2
Chociaż masz rację, z PO jasno wynika, że ​​chcemy to wiedzieć dla zdefiniowanych wartości. (PS: głos nie jest mój)
JVApen

1
@JVApen: To jest dokładnie problem. „Zdefiniowane wartości” nie są właściwością samego typu wyliczeniowego. Norma wyraźnie określa, jakie są wartości wyliczenia.
MSalters
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.