Jak mogę wyświetlić wartość klasy wyliczenia w C ++ 11

101

Jak mogę wyprowadzić wartość an enum classw C ++ 11? W C ++ 03 wygląda to tak:

#include <iostream>

using namespace std;

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

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

w c ++ 0x ten kod nie kompiluje się

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

opracowane na Ideone.com

Adi
źródło
1
Dlaczego próbujesz wyliczyć wyliczenie? Klasa enum służy do nie mieszania wartości wyliczenia z reprezentacją int
RiaD,

Odpowiedzi:

126

W przeciwieństwie do wyliczenia bez zakresu, wyliczenie w zakresie nie jest niejawnie konwertowane na jego wartość całkowitą. Musisz jawnie przekonwertować go na liczbę całkowitą za pomocą rzutowania:

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

Możesz chcieć hermetyzować logikę w szablonie funkcji:

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

użyty jako:

std::cout << as_integer(a) << std::endl;
James McNellis
źródło
3
Czy istnieje powód, dla którego używa końcowej składni zwracanego typu?
Nicol Bolas,
3
@NicolBolas: Skopiowałem as_integerz jednej z moich bibliotek open source, CxxReflect (patrz enumeration.hpp ). Biblioteka konsekwentnie używa końcowych typów zwracanych wszędzie. Dla spójności.
James McNellis,
11
Chociaż jest to 2 lata później, jeśli ktoś inny zobaczy to pytanie, możesz po prostu użyć powyższej metody rzutowania i po prostu wywołać „static_cast <int> (value)”, aby uzyskać liczbę całkowitą lub „static_cast <A> (intValue)”, aby uzyskać wartość wyliczeniową. Pamiętaj tylko, że przejście z int do enum lub enum do enum może powodować problemy i ogólnie jest oznaką błędu projektowego.
Benjamin Danger Johnson
4
int (value) i A (intValue) również działają bez brzydkich nawiasów kątowych.
Grault
4
as_integermożna zdefiniować constexprtak, aby można go było używać w kontekstach, w których potrzebne jest stałe wyrażenie.
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;
}
Na zawsze
źródło
Skopiowałem ten przykład dosłownie i skompilowałem go jako, g++ -std=c++0x enum.cppale otrzymuję kilka błędów kompilatora -> pastebin.com/JAtLXan9 . Nie mogłem również pobrać przykładu z @ james-mcnellis do skompilowania.
Dennis
4
@Dennis underlying_type jest tylko w C ++ 11
Deqing
23

Możliwe jest uzyskanie drugiego przykładu (tj. Tego, który używa wyliczenia o określonym zakresie), aby działał przy użyciu tej samej składni, co wyliczenia bez zakresu. Ponadto rozwiązanie jest ogólne i będzie działać dla wszystkich wyliczeń o określonym zakresie, w przeciwieństwie do pisania kodu dla każdego wyliczenia w zakresie (jak pokazano w odpowiedzi dostarczonej przez @ForEveR ).

Rozwiązaniem jest napisanie operator<<funkcji ogólnej, która będzie działać dla każdego wyliczenia o określonym zakresie. Rozwiązanie wykorzystuje SFINAE via std::enable_ifi wygląda następująco.

#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
źródło
Potrzebujesz typenamewcześniej std::underlying_type<T>::type.
uckelman
@uckelman Masz całkowitą rację. Dzięki za zaktualizowanie mojej odpowiedzi.
James Adkison,
to działało dla mnie pod clang, ale pod gcc 4.9.2 to rozwiązanie zawodzi podczas łączenia << razem z błędem error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’. wydaje się, że dzieje się tak, ponieważ gdy strumień jest tymczasowy, ADL kończy się niepowodzeniem, a powyższy szablon nie jest możliwy. jakieś wskazówki?
ofloveandhate
@ofloveandhate Czy możesz podać link do przykładu, w którym występuje problem? Przetestowałem powyższy kod w gcc 4.9.2 bez żadnych problemów i tylko z niewielką zmianą, przekonwertowałem 3 coutinstrukcje w jedną coutinstrukcję, łącząc <<operatory razem. Zobacz tutaj
James Adkison,
pozwól, że zrewiduję moje oświadczenie. Próbowałem wydrukować klasę wyliczenia zawartą w klasie, spoza tej klasy. powyższy kod rzeczywiście działa dla klas wyliczeniowych, które nie są zawarte w samej klasie.
ofloveandhate
10

(Nie wolno mi jeszcze komentować). Sugerowałbym następujące ulepszenia już świetnej odpowiedzi Jamesa McNellisa:

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

z

  • constexpr: pozwala mi użyć wartości elementu wyliczenia jako rozmiaru tablicy w czasie kompilacji
  • static_assert+ is_enum: aby 'zapewnić' w czasie kompilacji, że funkcja coś wykona. tylko z wyliczeniami, zgodnie z sugestią

Swoją drogą, zadaję sobie pytanie: dlaczego powinienem używać, enum classkiedy chciałbym przypisać wartości liczbowe moim członkom wyliczenia? Biorąc pod uwagę wysiłek związany z konwersją.

Być może wtedy wróciłbym do zwykłego, enumjak zasugerowałem tutaj: Jak używać wyliczeń jako flag w C ++?


Jeszcze inny (lepszy) smak bez static_assert, oparty na sugestii @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
źródło
Czy istnieje typ, Tdla którego std::underlying_type<T>::typeistnieje, ale std::is_enum<T>::valuejest fałszywy? Jeśli nie, to static_assertnie dodaje żadnej wartości.
Toby Speight
1
Nie testowałem na wszystkich kompilatorach. Ale, @TobySpeight, prawdopodobnie masz rację, msvc2013 wydaje się wypluwać zrozumiałe komunikaty o błędach, sugerując zgodność 1 do 1 między istniejącym base_type_t a samym typem wyliczonym. I static_assert nie jest nawet zwolniony. Ale: odwołanie mówi, że zachowanie podstawowego_typu jest niezdefiniowane, jeśli nie jest dostarczony z pełnym typem wyliczenia. Tak więc static_assert jest tylko nadzieją na uzyskanie maksymalnie zrozumiałej wiadomości na wypadek. Być może istnieją możliwości wymuszenia wcześniejszego / najwcześniejszego przetworzenia go?
yau,
Ach tak, masz rację, że jest niezdefiniowany, jeśli Enumerationnie jest pełnym typem wyliczenia. W takim przypadku może być już za późno, ponieważ jest używany w zwracanym typie. Może moglibyśmy określić std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>jako typ zwrotu? Oczywiście jest o wiele łatwiej (a komunikaty o błędach o wiele wyraźniejsze), jeśli masz kompilator z obsługą Concepts ...
Toby Speight,
8

Pisać prościej,

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

int value = static_cast<int>(Color::Blue); // 111
Audrius Meskauskas
źródło
To nie zadziała, gdy wyliczeniu zostanie wyraźnie określony typ bazowy
James
3

Pracowałem dla mnie w 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);
}
Orzechówka
źródło
0

Możesz zrobić coś takiego:

//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;
Hrabia Lemongrab
źródło
2
Pytanie zadawane o klasę enum.
Ant