Wie kann ich den Wert einer Enum-Klasse in C ++ 11 ausgeben?

95

Wie kann ich den Wert von a enum classin C ++ 11 ausgeben ? In C ++ 03 ist es so:

#include <iostream>

using namespace std;

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

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

In c ++ 0x wird dieser Code nicht kompiliert

#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]'

zusammengestellt bei Ideone.com

Adi
quelle
1
Warum versuchen Sie, eine Aufzählung auszugeben? Enum-Klasse wird verwendet, um Enum-Werte nicht mit Int-Darstellung zu
verwechseln

Antworten:

122

Im Gegensatz zu einer Aufzählung ohne Gültigkeitsbereich kann eine Aufzählung mit Gültigkeitsbereich nicht implizit in ihren ganzzahligen Wert konvertiert werden. Sie müssen es mithilfe einer Umwandlung explizit in eine Ganzzahl konvertieren:

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

Möglicherweise möchten Sie die Logik in eine Funktionsvorlage einkapseln:

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

benutzt als:

std::cout << as_integer(a) << std::endl;
James McNellis
quelle
3
Gibt es einen Grund, warum hier die Syntax des Trailing-Return-Typs verwendet wird?
Nicol Bolas
3
@NicolBolas: Ich habe as_integeraus einer meiner Open-Source-Bibliotheken, CxxReflect, kopiert (siehe enumeration.hpp ). Die Bibliothek verwendet überall konsistent nachfolgende Rückgabetypen. Aus Gründen der Konsistenz.
James McNellis
11
Obwohl dies 2 Jahre zu spät ist, können Sie, falls jemand anderes diese Frage sieht, einfach die oben beschriebene Cast-Technik verwenden und einfach "static_cast <int> (Wert)" aufrufen, um die Ganzzahl oder "static_cast <A> (intValue)" zu erhalten Holen Sie sich einen Aufzählungswert. Denken Sie daran, dass der Wechsel von int zu enum oder von enum zu enum Probleme verursachen kann und im Allgemeinen ein Zeichen für einen Designfehler ist.
Benjamin Danger Johnson
4
int (Wert) und A (intValue) funktionieren auch ohne die hässlichen spitzen Klammern.
Grault
4
as_integerkann so definiert werden constexpr, dass es in Kontexten verwendet werden kann, in denen ein konstanter Ausdruck erforderlich ist.
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;
}
Für immer
quelle
Ich habe dieses Beispiel wörtlich kopiert und als kompiliert, g++ -std=c++0x enum.cppaber es werden einige Compilerfehler angezeigt -> pastebin.com/JAtLXan9 . Ich konnte das Beispiel von @ james-mcnellis auch nicht kompilieren.
Dennis
4
@ Tennis zugrunde liegenden_Typ ist nur in C ++ 11
Deqing
23

Es ist möglich, dass Ihr zweites Beispiel (dh das mit einer Aufzählung mit Gültigkeitsbereich) mit derselben Syntax wie Aufzählungen ohne Gültigkeitsbereich funktioniert. Darüber hinaus ist die Lösung generisch und funktioniert für alle Enums mit Gültigkeitsbereich, während Code für jede Enumeration mit Gültigkeitsbereich geschrieben wird (wie in der Antwort von @ForEveR gezeigt) ).

Die Lösung besteht darin, eine generische operator<<Funktion zu schreiben, die für jede Enumeration mit Gültigkeitsbereich funktioniert. Die Lösung verwendet SFINAE über std::enable_ifund ist wie folgt.

#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
quelle
Du brauchst ein typenamevorher std::underlying_type<T>::type.
uckelman
@uckelman Du bist absolut richtig. Vielen Dank für die Aktualisierung meiner Antwort.
James Adkison
Dies funktionierte für mich unter clang, aber unter gcc 4.9.2 schlägt diese Lösung fehl, wenn << mit dem Fehler verkettet wird error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’. Dies scheint daran zu liegen, dass bei einem temporären Stream die ADL fehlschlägt und die obige Vorlage nicht möglich ist. Irgendwelche Tipps?
ofloveandhate
@ofloveandhate Können Sie einen Link zu einem Beispiel bereitstellen, das das Problem verursacht? Ich habe den obigen Code in gcc 4.9.2 ohne Probleme getestet und nur eine geringfügige Änderung vorgenommen. Ich habe die 3 coutAnweisungen in eine einzige coutAnweisung konvertiert, indem ich die <<Operatoren miteinander verkettet habe . Siehe hier
James Adkison
Lassen Sie mich meine Aussage überarbeiten. Ich habe versucht, eine Aufzählungsklasse zu drucken, die in einer Klasse enthalten ist, von außerhalb dieser Klasse. Der obige Code funktioniert tatsächlich für Enum-Klassen, die nicht in einer Klasse selbst enthalten sind.
ofloveandhate
10

(Ich darf noch keinen Kommentar abgeben.) Ich würde die folgenden Verbesserungen der bereits großartigen Antwort von James McNellis vorschlagen:

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

mit

  • constexpr: Erlaubt mir, einen Enum-Member-Wert als Array-Größe zur Kompilierungszeit zu verwenden
  • static_assert+ is_enum: um die Kompilierungszeit sicherzustellen, dass die Funktion etw. nur mit Aufzählungen, wie vorgeschlagen

Übrigens frage ich mich: Warum sollte ich jemals verwenden, enum classwenn ich meinen Enum-Mitgliedern Zahlenwerte zuweisen möchte?! Berücksichtigung des Konvertierungsaufwands.

Vielleicht würde ich dann wieder zum Normalen zurückkehren enum Normalen wie ich hier vorgeschlagen habe: Wie verwende ich Enums als Flags in C ++?


Noch eine (bessere) Variante davon ohne static_assert, basierend auf einem Vorschlag von @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
quelle
Gibt es einen Typ, Tfür den es std::underlying_type<T>::typegibt, der aber std::is_enum<T>::valuefalsch ist? Wenn nicht, dann static_assertfügt das keinen Wert hinzu.
Toby Speight
1
Ich habe nicht auf allen Compilern getestet. Aber @TobySpeight, Sie haben wahrscheinlich Recht, msvc2013 scheint verständliche Fehlermeldungen auszuspucken, was auf eine 1-zu-1-Entsprechung zwischen dem vorhandenen zugrunde liegenden_Typ_t und dem Typ selbst hinweist, der enum ist. Und static_assert wird nicht einmal ausgelöst. Aber: Die Referenz besagt, dass das Verhalten des zugrunde liegenden Typs undefiniert ist, wenn es nicht mit einem vollständigen Aufzählungstyp versehen ist. Der static_assert ist also nur eine Hoffnung, für den Fall eine maximal verständliche Nachricht zu erhalten. Vielleicht gibt es Möglichkeiten, eine frühere / früheste Verarbeitung zu erzwingen?
Yau
Ah ja, Sie haben Recht, dass es undefiniert ist, wenn Enumerationes sich nicht um einen vollständigen Aufzählungstyp handelt. In diesem Fall ist es möglicherweise bereits zu spät, da es im Rückgabetyp verwendet wird. Vielleicht könnten wir std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>als Rückgabetyp angeben ? Natürlich ist es so viel einfacher (und die Fehlermeldungen so viel klarer), wenn Sie einen Compiler mit Unterstützung für Konzepte haben ...
Toby Speight
5

Einfacher schreiben,

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

int value = static_cast<int>(Color::Blue); // 111
Audrius Meskauskas
quelle
Dies funktioniert nicht, wenn der Aufzählung explizit ein zugrunde liegender Typ zugewiesen wird
James
3

Folgendes hat in C ++ 11 für mich funktioniert:

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);
}
Nussknacker
quelle
0

Sie könnten so etwas tun:

//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;
Earl of Lemongrab
quelle
1
Die Frage stellte sich nach einer Aufzählungsklasse.
Ameise