Enum continuo C ++ 11


17

C'è un modo per verificare in C ++ 11 se un enum è continuo ?

È pienamente valido fornire valori enum che non lo sono. C'è forse una caratteristica come una caratteristica del tipo in C ++ 14, C ++ 17 o forse C ++ 20 per verificare se l'enum è continuo? Questo da usare in un static_assert.

Segue un piccolo esempio:

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
I mezzi continuano, che ha un ordine crescente o significa che inizia con zero e quindi +1 per ogni valore?
RoQuOTriX

5
Non è possibile enumerare le etichette di enumerazione, quindi non è possibile farlo dall'interno del programma stesso.
Qualche programmatore amico

1
Interessante. Sto pensando lungo la linea di programmazione dei template seguendo le linee su come ottenere un compilatore per calcolare un fattoriale. Inizieresti con i due limiti A e C, e le funzioni del template controllano tramite SFINAE la presenza o meno di tutti i valori tra loro nel file enum. Purtroppo ho un lavoro diurno, quindi non posso tentare di scriverlo, anche se voterò una risposta basata su questo approccio. Sono abbastanza sicuro che qualcuno come @barry o @sehe potrebbe farlo.
Bathsheba,

1
@RoQuOTriX Come assoceresti un valore a un'etichetta? E come verifichi l'ordine delle etichette? E come potrebbe essere fatto in fase di compilazione (che è necessario per static_assert)? Anche se non riesci a trovare una "bella soluzione", ti preghiamo di scrivere comunque una risposta poiché sono molto curioso di sapere come si possa fare in modo generico.
Qualche programmatore amico

1
@Someprogrammerdude quello che hai descritto è la "bella" o buona soluzione. Quello che intendevo era la soluzione di controllo "facile", che dovresti riscrivere per ogni enum e che Dio ti benedica, spero che nessuno lo faccia
RoQuOTriX

Risposte:


7

Per un certo numero di enums puoi probabilmente farti strada attraverso questo usando la libreria Magic Enum . Per esempio:

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

Si noti che questo è effettivamente, come suggerisce il nome della libreria, "magico" - la libreria funziona su un numero di hack specifici del compilatore. In quanto tale, in realtà non soddisfa il tuo requisito di "puro C ++", ma è probabilmente il massimo che possiamo ottenere fino a quando non avremo strutture di riflessione nel linguaggio.


È davvero magico, ma questo farebbe al meglio con la mia situazione.
Bart

7

Ciò non è possibile nel C ++ puro, poiché non è possibile enumerare i valori enum o scoprire il numero dei valori e i valori minimo e massimo. Ma potresti provare a utilizzare l'aiuto del tuo compilatore per implementare qualcosa di simile a quello che desideri. Ad esempio, in gcc è possibile imporre un errore di compilazione se switchun'istruzione non gestisce tutti i valori di un enum:

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

Ovviamente, questo è specializzato per un dato enum, ma la definizione di tali funzioni può essere automatizzata con il preprocessore.


Se ho capito bene questo richiederebbe comunque di scrivere tutti i valori enum nello switch e nell'elenco per minmax. Attualmente ho più enumerazioni quindi è davvero possibile ma non preferito per la mia situazione.
Bart,

1

Mi piacerebbe vedere una risposta su questo. Ne ho avuto bisogno anche io.

Sfortunatamente, non penso che ciò sia possibile utilizzando le utility esistenti. Se vuoi implementare un tratto di tipo su questo, hai bisogno del supporto del tuo compilatore, quindi scrivere un modello per questo non sembra fattibile.

Ho già esteso l'enumerazione con un tag specifico per indicare che è contiguo e ti dà immediatamente la dimensione: costruttore di classi enum c ++, come passare un valore specifico?

In alternativa, puoi scrivere il tuo tratto personale:

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

Questo deve essere specializzato ogni volta che si definisce un enigma contiguo in cui si desidera utilizzarlo. Sfortunatamente, ciò richiede un po 'di manutenzione e attenzione se l'enum viene modificato.


1
È possibile scrivere un controllo codice, che controlla durante la compilazione, se il tipo è impostato correttamente
RoQuOTriX

Si Certamente. Se hai la possibilità di scriverlo.
JVApen

1

Tutti gli enum sono continui. 0 è sempre consentito; il valore più alto consentito è l'enumeratore più alto arrotondato per 1<<N -1eccesso al successivo (tutti i bit uno) e sono consentiti anche tutti i valori in mezzo. ([dcl.enum] 9.7.1 / 5). Se sono stati definiti enumeratori negativi, il valore più basso consentito viene definito in modo simile arrotondando per difetto l'enumeratore più basso.

Gli enumeratori definiti in enumsono espressioni costanti con un valore nell'intervallo e il tipo corretto, ma è possibile definire costanti aggiuntive al di fuori delle enumquali hanno le stesse proprietà:

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


2
Sebbene tu abbia ragione, dall'OP è chiaro che vogliamo sapere questo per i valori definiti. (PS: il voto
negativo

1
@JVApen: questo è il problema esatto. I "valori definiti" non sono una proprietà del tipo enum stesso. Lo standard è esplicito quali sono i valori dell'enum.
Salta il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.