Como posso gerar o valor de uma classe enum em C ++ 11

96

Como posso gerar o valor de um enum classem C ++ 11? Em C ++ 03 é assim:

#include <iostream>

using namespace std;

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

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

em c ++ 0x este código não 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 em Ideone.com

Adi
fonte
1
Por que você está tentando gerar enum? classe enum é usada para não misturar valores de enum com representação int
RiaD

Respostas:

122

Ao contrário de uma enumeração sem escopo, uma enumeração com escopo não é implicitamente conversível em seu valor inteiro. Você precisa explicitamente convertê-lo em um inteiro usando um elenco:

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

Você pode querer encapsular a lógica em um modelo de função:

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
fonte
3
Existe uma razão para isso usar sintaxe de tipo de retorno à direita?
Nicol Bolas
3
@NicolBolas: Copiei as_integerde uma das minhas bibliotecas de código aberto, CxxReflect (consulte enumeration.hpp ). A biblioteca usa tipos de retorno à direita de forma consistente, em todos os lugares. Para consistência.
James McNellis
11
Embora este seja 2 anos atrasado, no caso de alguém ver esta questão, você pode apenas usar o método da técnica de elenco acima e simplesmente chamar "static_cast <int> (valor)" para obter o inteiro ou "static_cast <A> (intValue)" para obter um valor enum. Apenas tenha em mente que ir de int para enum ou de enum para enum pode causar problemas e geralmente é um sinal de um bug de design.
Benjamin Danger Johnson
4
int (valor) e A (intValue) também funcionam, sem os colchetes angulares feios.
Grault
4
as_integerpode ser definido para ser constexprusado em contextos onde a expressão constante é necessária.
Nawaz de
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;
}
Para sempre
fonte
Copiei este exemplo literalmente e o compilei como, g++ -std=c++0x enum.cppmas estou recebendo um monte de erros do compilador -> pastebin.com/JAtLXan9 . Eu também não consegui obter o exemplo de @james-mcnellis para compilar.
Dennis
4
@Dennis subjacente_type está apenas em C ++ 11
Deqing
23

É possível fazer com que seu segundo exemplo (ou seja, aquele que usa um enum com escopo definido) funcione usando a mesma sintaxe dos enums sem escopo. Além disso, a solução é genérica e funcionará para todos os enums com escopo definido, em vez de escrever código para cada enum com escopo definido (conforme mostrado na resposta fornecida por @ForEveR ).

A solução é escrever uma operator<<função genérica que funcionará para qualquer enum com escopo definido. A solução emprega SFINAE via std::enable_ife é a seguinte.

#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
fonte
Você precisa de um typenameantes std::underlying_type<T>::type.
uckelman
@uckelman Você está absolutamente correto. Obrigado por atualizar minha resposta.
James Adkison
isso funcionou para mim no clang, mas no gcc 4.9.2, essa solução falha ao encadear << juntos, com o erro error: cannot bind ‘std::basic_ostream<char>’ lvalue to ‘std::basic_ostream<char>&&’. isso parece ser porque quando o fluxo é temporário, o ADL falha e o modelo acima não é uma possibilidade. alguma dica?
ofloveandhate
@ofloveandhate Você poderia fornecer um link para um exemplo que produza o problema? Testei o código acima no gcc 4.9.2 sem problemas e apenas uma pequena mudança, converti as 3 coutinstruções em uma única coutinstrução encadeando os <<operadores. Veja aqui
James Adkison
deixe-me revisar minha declaração. Eu estava tentando imprimir uma classe enum contida dentro de uma classe, de fora dessa classe. o código acima realmente funciona para classes enum não contidas em uma classe em si.
ofloveandhate
10

(Não tenho permissão para comentar ainda.) Eu sugeriria as seguintes melhorias para a já excelente resposta 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);
}

com

  • constexpr: permitindo-me usar um valor de membro enum como tamanho de array em tempo de compilação
  • static_assert+ is_enum: para 'garantir' o tempo de compilação que a função executa sth. com enumerações apenas, como sugerido

A propósito, estou me perguntando: por que devo usar enum classquando gostaria de atribuir valores numéricos aos membros de minha enumeração ?! Considerando o esforço de conversão.

Talvez eu voltasse ao normal, enumcomo sugeri aqui: Como usar enums como sinalizadores em C ++?


Mais um sabor (melhor) sem static_assert, baseado na sugestão 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
fonte
Existe um tipo Tpara o qual std::underlying_type<T>::typeexiste, mas std::is_enum<T>::valueé falso? Se não, então o static_assertadiciona nenhum valor.
Toby Speight
1
Não testei em todos os compiladores. Mas, @TobySpeight, você provavelmente está certo, msvc2013 parece cuspir mensagens de erro compreensíveis, sugerindo uma correspondência 1 para 1 entre subjacente_type_t existente e o próprio tipo sendo enum. E static_assert nem mesmo é disparado. Mas: a referência diz que o comportamento de basic_type é indefinido se não for fornecido com um tipo de enum completo. Portanto, o static_assert é apenas uma esperança de obter uma mensagem máxima compreensível no caso. Talvez haja possibilidades de forçá-lo a ser processado mais cedo / mais cedo?
yau
Ah sim, você está certo que é indefinido se Enumerationnão for um tipo de enum completo. Nesse caso, pode já ser tarde demais, pois é usado no tipo de retorno. Talvez pudéssemos especificar std::enable_if<std::is_enum<Enumeration>::value, std::underlying_type<Enumeration>::type>como o tipo de retorno? Claro, é muito mais fácil (e as mensagens de erro muito mais claras) se você tiver um compilador com suporte para Conceitos ...
Toby Speight
6

Para escrever mais simples,

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

int value = static_cast<int>(Color::Blue); // 111
Audrius Meskauskas
fonte
Isso não funcionará quando o enum receber explicitamente um tipo subjacente
Tiago,
3

A seguir funcionou para mim em 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);
}
NutCracker
fonte
0

Você poderia fazer algo assim:

//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
fonte
2
A pergunta feita sobre uma classe enum.
Formiga