¿Cómo puedo generar el valor de una clase enum en C ++ 11?

96

¿Cómo puedo generar el valor de an enum classen C ++ 11? En C ++ 03 es así:

#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 este código no se compila

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

compilado en Ideone.com

Adi
fuente
1
¿Por qué estás intentando generar una enumeración? La clase enum se usa para no mezclar valores de enumeración con representación int
RiaD

Respuestas:

122

A diferencia de una enumeración sin ámbito, una enumeración con ámbito no se puede convertir implícitamente en su valor entero. Necesita convertirlo explícitamente en un número entero usando una conversión:

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

Es posible que desee encapsular la lógica en una plantilla de función:

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

Usado como:

std::cout << as_integer(a) << std::endl;
James McNellis
fuente
3
¿Hay alguna razón por la que esto use la sintaxis de tipo de retorno final?
Nicol Bolas
3
@NicolBolas: Copié as_integerde una de mis bibliotecas de código abierto, CxxReflect (ver enumeration.hpp ). La biblioteca usa tipos de retorno finales de manera consistente, en todas partes. Por consistencia.
James McNellis
11
Aunque esto es 2 años tarde, en caso de que alguien más vea esta pregunta, puede usar el método de la técnica de conversión anterior y simplemente llamar a "static_cast <int> (value)" para obtener el número entero o "static_cast <A> (intValue)" para obtener un valor de enumeración. Solo tenga en cuenta que pasar de int a enum o de enum a enum puede causar problemas y, en general, es una señal de un error de diseño.
Benjamin Danger Johnson
4
int (valor) y A (intValue) también funcionan, sin los feos corchetes angulares.
Grault
4
as_integerse puede definir para constexprque se pueda usar en contextos donde se necesita una expresión constante.
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;
}
Siempre
fuente
Copié este ejemplo palabra por palabra y lo compilé como, g++ -std=c++0x enum.cpppero recibo un montón de errores de compilación -> pastebin.com/JAtLXan9 . Tampoco pude obtener el ejemplo de @ james-mcnellis para compilar.
Dennis
4
@Dennis underlying_type es sólo en C ++ 11
Deqing
23

Es posible hacer que su segundo ejemplo (es decir, el que usa una enumeración con ámbito) funcione usando la misma sintaxis que las enumeraciones sin ámbito. Además, la solución es genérica y funcionará para todas las enumeraciones con ámbito, en lugar de escribir código para cada enumeración con ámbito (como se muestra en la respuesta proporcionada por @ForEveR ).

La solución es escribir una operator<<función genérica que funcione para cualquier enumeración con ámbito. La solución emplea SFINAE vía std::enable_ify es la siguiente.

#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
fuente
Necesitas un typenameantes std::underlying_type<T>::type.
uckelman
@uckelman Tienes toda la razón. Gracias por actualizar mi respuesta.
James Adkison
esto funcionó para mí bajo clang, pero bajo gcc 4.9.2, esta solución falla al encadenar << juntos, con el error error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’. esto parece deberse a que cuando la transmisión es temporal, la ADL falla y la plantilla anterior no es posible. ¿algun consejo?
ofloveandhate
@ofloveandhate ¿Podría proporcionar un enlace a un ejemplo que produzca el problema? Probé el código anterior en gcc 4.9.2 sin ningún problema y solo con un ligero cambio, convertí las 3 coutdeclaraciones en una sola coutdeclaración encadenando los <<operadores. Vea aquí
James Adkison
déjeme revisar mi declaración. Estaba tratando de imprimir una clase de enumeración contenida dentro de una clase, desde fuera de esa clase. el código anterior de hecho funciona para las clases de enumeración que no están contenidas dentro de una clase.
ofloveandhate
10

(No puedo comentar todavía). Sugeriría las siguientes mejoras a la ya excelente respuesta 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);
}

con

  • constexpr: permitiéndome usar un valor de miembro de enumeración como tamaño de matriz en tiempo de compilación
  • static_assert+ is_enum: para 'asegurar' en tiempo de compilación que la función hace algo. solo con enumeraciones, como se sugiere

Por cierto, me pregunto: ¿Por qué debería usar enum classcuando me gustaría asignar valores numéricos a los miembros de mi enumeración? Considerando el esfuerzo de conversión.

Quizás luego volvería a lo ordinario enumcomo sugerí aquí: ¿Cómo usar enumeraciones como indicadores en C ++?


Otro sabor (mejor) sin static_assert, basado en una sugerencia 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
fuente
¿Existe un tipo Tpara el que std::underlying_type<T>::typeexiste, pero std::is_enum<T>::valuees falso? Si no es así, no static_assertagrega valor.
Toby Speight
1
No probé en todos los compiladores. Pero, @TobySpeight probablemente tenga razón, msvc2013 parece escupir mensajes de error comprensibles, lo que sugiere una correspondencia 1 a 1 entre subyacente_tipo_t existente y el tipo en sí mismo es enumeración. Y static_assert ni siquiera se activa. Pero: la referencia dice que el comportamiento del tipo de contenido subyacente no está definido si no se proporciona un tipo de enumeración completo. Entonces, static_assert es solo una esperanza de obtener un mensaje máximo comprensible en caso de que lo haga. ¿Quizás hay posibilidades de forzar que se procese antes / antes?
yau
Ah, sí, tienes razón en que no está definido si Enumerationno es un tipo de enumeración completo. En cuyo caso, puede que ya sea demasiado tarde, ya que se usa en el tipo de devolución. ¿Quizás podríamos especificar std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>como tipo de retorno? Por supuesto, es mucho más fácil (y los mensajes de error mucho más claros) si tiene un compilador compatible con Concepts ...
Toby Speight
7

Para escribir más simple,

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

int value = static_cast<int>(Color::Blue); // 111
Audrius Meskauskas
fuente
Esto no funcionará cuando se le da explícitamente a la enumeración un tipo subyacente
James
3

Lo siguiente funcionó para mí 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);
}
Cascanueces
fuente
0

Podrías hacer algo como esto:

//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;
Conde de Lemongrab
fuente
2
La pregunta sobre una clase de enumeración.
Ant