Énumération continue C ++ 11

17

Existe-t-il un moyen de vérifier en C ++ 11 si une énumération est continue ?

Il est tout à fait valide de donner des valeurs d'énumération qui ne le sont pas. Existe-t-il peut-être une fonctionnalité comme un trait de type en C ++ 14, C ++ 17 ou peut-être C ++ 20 pour vérifier si l'énumération est continue? À utiliser dans un static_assert.

Un petit exemple suit:

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
Bart
la source
1
Signifie continue, qu'il a un ordre croissant ou cela signifie est commence par zéro, puis +1 pour chaque valeur?
RoQuOTriX
5
Il n'y a aucun moyen d'énumérer les étiquettes d'énumération, il n'est donc pas possible de le faire depuis l'intérieur du programme lui-même.
Un mec programmeur le
1
Intéressant. Je pense le long de la programmation de modèles le long de la façon dont vous pouvez obtenir un compilateur pour calculer une factorielle. Vous lanceriez la chose avec les deux bornes A et C, et les fonctions de modèle vérifient via SFINAE la présence ou non de toutes les valeurs entre elles dans le enum. Malheureusement, j'ai un travail de jour, je ne peux donc pas essayer de l'écrire, même si je vais voter pour une réponse basée sur cette approche. Je suis sûr que quelqu'un comme @barry ou @sehe pourrait le faire.
Bathsheba
1
@RoQuOTriX Comment associeriez-vous une valeur à une étiquette? Et comment vérifieriez-vous l'ordre des étiquettes? Et comment cela pourrait-il être fait au moment de la compilation (ce qui est nécessaire pour static_assert)? Même si vous ne pouvez pas faire une "belle solution", veuillez quand même écrire une réponse car je suis très curieux de savoir comment cela pourrait être fait de manière générique.
Un programmeur du
1
@Someprogrammerdude ce que vous avez décrit est la "belle" ou la bonne solution. Ce que je voulais dire, c'était la solution de vérification "facile", que vous auriez à réécrire pour chaque enum et que Dieu bénisse, j'espère que personne ne le
fera

Réponses:

7

Pour un certain nombre de enums, vous pouvez probablement vous frayer un chemin à travers cela en utilisant la bibliothèque Magic Enum . Par exemple:

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

Notez que c'est en effet, comme le nom de la bibliothèque l'indique, "magique" - la bibliothèque fonctionne sur un certain nombre de hacks spécifiques au compilateur. En tant que tel, il ne répond pas vraiment à votre exigence de "C ++ pur", mais est probablement aussi bon que possible jusqu'à ce que nous ayons des installations de réflexion dans le langage.

N. Shead
la source
C'est en effet magique mais cela conviendrait le mieux à ma situation.
Bart
7

Cela n'est pas possible en C ++ pur, car il n'y a aucun moyen d'énumérer les valeurs d'énumération ou de découvrir le nombre de valeurs et les valeurs minimales et maximales. Mais vous pouvez essayer d'utiliser l'aide de votre compilateur pour implémenter quelque chose proche de ce que vous voulez. Par exemple, dans gcc, il est possible d'appliquer une erreur de compilation si une switchinstruction ne gère pas toutes les valeurs d'une énumération:

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

Évidemment, ceci est spécialisé pour une énumération donnée, mais la définition de ces fonctions peut être automatisée avec un préprocesseur.

Andrey Semashev
la source
Si je comprends bien, cela nécessiterait toujours d'écrire toutes les valeurs d'énumération dans le commutateur et la liste pour minmax. Actuellement, j'ai plusieurs énumérations, il est donc possible mais pas préféré pour ma situation.
Bart
1

J'aimerais voir une réponse à ce sujet. J'en ai aussi eu besoin.

Malheureusement, je ne pense pas que cela soit possible en utilisant les utilitaires existants. Si vous voulez implémenter un trait de type sur cela, vous avez besoin du support de votre compilateur, donc écrire un modèle pour cela ne semble pas faisable.

J'ai déjà étendu l'énumération avec une balise spécifique pour indiquer qu'elle est contiguë et vous donne immédiatement la taille: constructeur de classe enum c ++, comment passer une valeur spécifique?

Alternativement, vous pouvez écrire votre propre trait:

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

Cela doit être spécialisé chaque fois que vous définissez une énumération contiguë où vous souhaitez l'utiliser. Malheureusement, cela nécessite une maintenance et une attention si l'énumération est modifiée.

JVApen
la source
1
Vous pouvez écrire un vérificateur de code, qui vérifie lors de la compilation, si le type est défini correctement
RoQuOTriX
Oui en effet. Si vous avez la capacité de l'écrire.
JVApen
1

Toutes les énumérations sont continues. 0 est toujours autorisé; la valeur la plus élevée autorisée est l'énumérateur le plus élevé arrondi au suivant 1<<N -1(tous les bits un), et toutes les valeurs intermédiaires sont également autorisées. ([dcl.enum] 9.7.1 / 5). Si des énumérateurs négatifs sont définis, la valeur la plus basse autorisée est définie de la même manière en arrondissant l'énumérateur le plus bas.

Les énumérateurs définis dans le enumsont des expressions constantes avec une valeur dans la plage et le type correct, mais vous pouvez définir des constantes supplémentaires en dehors de enumqui ont les mêmes propriétés:

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

MSalters
la source
2
Bien que vous ayez raison, il ressort clairement de l'OP que nous voulons le savoir pour les valeurs définies. (PS: le vote à la baisse n'est pas le mien)
JVApen
1
@JVApen: C'est exactement le problème. Les "valeurs définies" ne sont pas une propriété du type enum lui-même. La norme est explicite quelles sont les valeurs de l'énumération.
MSalters