Comment puis-je générer la valeur d'une classe enum en C ++ 11

96

Comment puis-je générer la valeur d'un enum classen C ++ 11? En C ++ 03, c'est comme ça:

#include <iostream>

using namespace std;

enum A {
  a = 1,
  b = 69,
  c= 666
};

int main () {
  A a = A::c;
  cout << a << endl;
}

en c ++ 0x ce code ne compile pas

#include <iostream>

using namespace std;

enum class A {
  a = 1,
  b = 69,
  c= 666
};

int main () {
  A a = A::c;
  cout << a << endl;
}


prog.cpp:13:11: error: cannot bind 'std::ostream' lvalue to 'std::basic_ostream<char>&&'
/usr/lib/gcc/i686-pc-linux-gnu/4.5.1/../../../../include/c++/4.5.1/ostream:579:5: error:   initializing argument 1 of 'std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = A]'

compilé sur Ideone.com

Adi
la source
1
Pourquoi essayez-vous de sortir enum? la classe enum est utilisée pour ne pas mélanger les valeurs enum avec la représentation int
RiaD

Réponses:

122

Contrairement à une énumération non cadrée, une énumération étendue n'est pas implicitement convertible en sa valeur entière. Vous devez le convertir explicitement en entier à l'aide d'un cast:

std::cout << static_cast<std::underlying_type<A>::type>(a) << std::endl;

Vous souhaiterez peut-être encapsuler la logique dans un modèle de fonction:

template <typename Enumeration>
auto as_integer(Enumeration const value)
    -> typename std::underlying_type<Enumeration>::type
{
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

utilisé comme:

std::cout << as_integer(a) << std::endl;
James McNellis
la source
3
Y a-t-il une raison pour laquelle cela utilise la syntaxe de type de retour de fin?
Nicol Bolas
3
@NicolBolas: J'ai copié à as_integerpartir de l'une de mes bibliothèques open-source, CxxReflect (voir enumeration.hpp ). La bibliothèque utilise les types de retour de fin de manière cohérente, partout. Pour la cohérence.
James McNellis
11
Bien que ce soit 2 ans de retard, au cas où quelqu'un d'autre verrait cette question, vous pouvez simplement utiliser la méthode de la technique de cast ci-dessus et appeler simplement "static_cast <int> (value)" pour obtenir l'entier ou "static_cast <A> (intValue)" pour obtenir une valeur enum. Gardez simplement à l'esprit que passer de int à enum ou enum à enum peut causer des problèmes et est généralement le signe d'un bogue de conception.
Benjamin Danger Johnson
4
int (valeur) et A (intValue) fonctionnent également, sans les affreux crochets angulaires.
Grault
4
as_integerpeut être défini de constexprmanière à pouvoir être utilisé dans des contextes où une expression constante est nécessaire.
Nawaz
39
#include <iostream>
#include <type_traits>

using namespace std;

enum class A {
  a = 1,
  b = 69,
  c= 666
};

std::ostream& operator << (std::ostream& os, const A& obj)
{
   os << static_cast<std::underlying_type<A>::type>(obj);
   return os;
}

int main () {
  A a = A::c;
  cout << a << endl;
}
Pour toujours
la source
J'ai copié cet exemple mot pour mot et l' g++ -std=c++0x enum.cppai compilé comme mais j'obtiens un tas d'erreurs de compilation -> pastebin.com/JAtLXan9 . Je n'ai pas non plus pu obtenir l'exemple de @ james-mcnellis à compiler.
Dennis
4
@Dennis sous- jacent_type est uniquement en C ++ 11
Deqing
23

Il est possible de faire fonctionner votre deuxième exemple (c'est-à-dire celui qui utilise une énumération à portée) en utilisant la même syntaxe que les énumérations sans portée. De plus, la solution est générique et fonctionnera pour toutes les énumérations de portée, par opposition à l'écriture de code pour chaque énumération de portée (comme indiqué dans la réponse fournie par @ForEveR ).

La solution consiste à écrire une operator<<fonction générique qui fonctionnera pour toute énumération de portée. La solution utilise SFINAE via std::enable_ifet est la suivante.

#include <iostream>
#include <type_traits>

// Scoped enum
enum class Color
{
    Red,
    Green,
    Blue
};

// Unscoped enum
enum Orientation
{
    Horizontal,
    Vertical
};

// Another scoped enum
enum class ExecStatus
{
    Idle,
    Started,
    Running
};

template<typename T>
std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream, const T& e)
{
    return stream << static_cast<typename std::underlying_type<T>::type>(e);
}

int main()
{
    std::cout << Color::Blue << "\n";
    std::cout << Vertical << "\n";
    std::cout << ExecStatus::Running << "\n";
    return 0;
}
James Adkison
la source
Vous avez besoin d'un typenameavant std::underlying_type<T>::type.
uckelman
@uckelman Vous avez absolument raison. Merci d'avoir mis à jour ma réponse.
James Adkison
cela a fonctionné pour moi sous clang, mais sous gcc 4.9.2, cette solution échoue lors du chaînage << ensemble, avec l'erreur error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’. cela semble être dû au fait que lorsque le flux est temporaire, l'ADL échoue et le modèle ci-dessus n'est pas une possibilité. des conseils?
ofloveandhate
@ofloveandhate Pourriez-vous fournir un lien vers un exemple qui produit le problème? J'ai testé le code ci-dessus dans gcc 4.9.2 sans aucun problème et seulement un léger changement, j'ai converti les 3 coutinstructions en une seule coutinstruction en enchaînant les <<opérateurs ensemble. Voir ici
James Adkison
laissez-moi réviser ma déclaration. J'essayais d'imprimer une classe enum contenue dans une classe, depuis l'extérieur de cette classe. le code ci-dessus fonctionne en effet pour les classes enum non contenues dans une classe elles-mêmes.
ofloveandhate
10

(Je ne suis pas encore autorisé à commenter.) Je suggérerais les améliorations suivantes à la réponse déjà excellente de James McNellis:

template <typename Enumeration>
constexpr auto as_integer(Enumeration const value)
    -> typename std::underlying_type<Enumeration>::type
{
    static_assert(std::is_enum<Enumeration>::value, "parameter is not of type enum or enum class");
    return static_cast<typename std::underlying_type<Enumeration>::type>(value);
}

avec

  • constexpr: me permettant d'utiliser une valeur de membre enum comme taille de tableau au moment de la compilation
  • static_assert+ is_enum: pour 'assurer' à la compilation que la fonction fait qc. avec énumérations seulement, comme suggéré

Au fait, je me demande: pourquoi devrais-je utiliser enum classquand je voudrais attribuer des valeurs numériques à mes membres enum?! Compte tenu de l'effort de conversion.

Peut-être que je retournerais alors à l'ordinaire enumcomme je l'ai suggéré ici: Comment utiliser les énumérations comme indicateurs en C ++?


Encore une autre (meilleure) saveur sans static_assert, basée sur une suggestion de @TobySpeight:

template <typename Enumeration>
constexpr std::enable_if_t<std::is_enum<Enumeration>::value,
std::underlying_type_t<Enumeration>> as_number(const Enumeration value)
{
    return static_cast<std::underlying_type_t<Enumeration>>(value);
}
yau
la source
Existe-t-il un type Tpour lequel std::underlying_type<T>::typeexiste, mais qui std::is_enum<T>::valueest faux? Sinon, alors static_assertn'ajoute aucune valeur.
Toby Speight
1
Je n'ai pas testé sur tous les compilateurs. Mais, @TobySpeight vous avez probablement raison, msvc2013 semble cracher des messages d'erreur compréhensibles, suggérant une correspondance 1-à-1 entre sous-jacent_type_t existant et le type lui-même étant enum. Et static_assert n'est même pas viré. Mais: la référence indique que le comportement de sous-jacent_type est indéfini s'il n'est pas fourni avec un type enum complet. Donc, static_assert est juste un espoir d'obtenir un message compréhensible maximum au cas où. Peut-être existe-t-il des possibilités de forcer son traitement plus tôt / plus tôt?
yau
Ah oui, vous avez raison de dire que ce n'est pas défini si ce Enumerationn'est pas un type enum complet. Dans ce cas, il est peut-être déjà trop tard, car il est utilisé dans le type de retour. Peut-être pourrions-nous spécifier std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>comme type de retour? Bien sûr, c'est tellement plus facile (et les messages d'erreur tellement plus clairs) si vous avez un compilateur prenant en charge les concepts ...
Toby Speight
6

Pour écrire plus simple,

enum class Color
{
    Red = 1,
    Green = 11,
    Blue = 111
};

int value = static_cast<int>(Color::Blue); // 111
Audrius Meskauskas
la source
Cela ne fonctionnera pas lorsque l'énumération reçoit explicitement un type sous-jacent
James
3

La suite a travaillé pour moi en C ++ 11:

template <typename Enum>
constexpr typename std::enable_if<std::is_enum<Enum>::value,
                                  typename std::underlying_type<Enum>::type>::type
to_integral(Enum const& value) {
    return static_cast<typename std::underlying_type<Enum>::type>(value);
}
Casse Noisette
la source
0

Vous pouvez faire quelque chose comme ceci:

//outside of main
namespace A
{
    enum A
    {
        a = 0,
        b = 69,
        c = 666
    };
};

//in main:

A::A a = A::c;
std::cout << a << std::endl;
Comte de Lemongrab
la source
2
La question posée sur une classe enum.
Ant