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


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;

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

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

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

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

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

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;

2
La question posée sur une classe enum.
Ant
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.