Como converter automaticamente enum fortemente tipado em int?

165
#include <iostream>

struct a {
  enum LOCAL_A { A1, A2 };
};
enum class b { B1, B2 };

int foo(int input) { return input; }

int main(void) {
  std::cout << foo(a::A1) << std::endl;
  std::cout << foo(static_cast<int>(b::B2)) << std::endl;
}

O a::LOCAL_Aé o que o enum fortemente digitado está tentando alcançar, mas há uma pequena diferença: enums normais pode ser convertido em tipo inteiro, enquanto enums rigidez não pode fazê-lo sem um elenco.

Portanto, existe uma maneira de converter um valor de enumeração fortemente tipado em um tipo inteiro sem uma conversão? Se sim, como?

BЈовић
fonte

Respostas:

134

Enums fortemente tipadas, com o objetivo de resolver vários problemas e não apenas o problema de escopo, como você mencionou na sua pergunta:

  1. Forneça segurança de tipo, eliminando assim a conversão implícita em número inteiro por promoção integral.
  2. Especifique os tipos subjacentes.
  3. Forneça escopo forte.

Portanto, é impossível converter implicitamente uma enumeração fortemente tipada em números inteiros ou mesmo no tipo subjacente - essa é a ideia. Então você precisa usar static_castpara tornar a conversão explícita.

Se o seu único problema é o escopo e você realmente deseja ter uma promoção implícita para números inteiros, é melhor usar enum não fortemente tipado com o escopo da estrutura em que está declarado.

LF
fonte
2
Esse é outro exemplo estranho de 'sabemos melhor o que você quer fazer' dos criadores de C ++. As enumerações convencionais (no estilo antigo) traziam muitos benefícios, como conversão implícita em índices, uso contínuo de operações bit a bit etc. As enumerações do novo estilo adicionavam um ótimo escopo, mas ... Você não pode usar exatamente isso especificação de tipo subjacente!). Então agora você é forçado a usar enums de estilo antigo com truques como colocá-los em struct ou criar soluções mais feias para novas enums, como criar seu próprio wrapper em torno de std :: vector apenas para superar essa coisa de CAST. sem comentários
avtomaton 10/06
152

Como outros já disseram, você não pode ter uma conversão implícita, e isso é por projeto.

Se você quiser, pode evitar a necessidade de especificar o tipo subjacente no elenco.

template <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
    return static_cast<typename std::underlying_type<E>::type>(e);
}

std::cout << foo(to_underlying(b::B2)) << std::endl;
R. Martinho Fernandes
fonte
75

Uma versão em C ++ 14 da resposta fornecida por R. Martinho Fernandes seria:

#include <type_traits>

template <typename E>
constexpr auto to_underlying(E e) noexcept
{
    return static_cast<std::underlying_type_t<E>>(e);
}

Como na resposta anterior, isso funcionará com qualquer tipo de enum e tipo subjacente. Eu adicionei a noexceptpalavra-chave, pois ela nunca lançará uma exceção.


Atualização
Isso também aparece no Effective Modern C ++ de Scott Meyers . Veja o item 10 (está detalhado nas páginas finais do item na minha cópia do livro).

Esqueleto de Classe
fonte
18
#include <cstdlib>
#include <cstdio>
#include <cstdint>

#include <type_traits>

namespace utils
{

namespace details
{

template< typename E >
using enable_enum_t = typename std::enable_if< std::is_enum<E>::value, 
                                               typename std::underlying_type<E>::type 
                                             >::type;

}   // namespace details


template< typename E >
constexpr inline details::enable_enum_t<E> underlying_value( E e )noexcept
{
    return static_cast< typename std::underlying_type<E>::type >( e );
}   


template< typename E , typename T>
constexpr inline typename std::enable_if< std::is_enum<E>::value &&
                                          std::is_integral<T>::value, E
                                         >::type 
 to_enum( T value ) noexcept 
 {
     return static_cast<E>( value );
 }

} // namespace utils




int main()
{
    enum class E{ a = 1, b = 3, c = 5 };

    constexpr auto a = utils::underlying_value(E::a);
    constexpr E    b = utils::to_enum<E>(5);
    constexpr auto bv = utils::underlying_value(b);

    printf("a = %d, b = %d", a,bv);
    return 0;
}
Khurshid Normuradov
fonte
3
Isso não reduz a digitação ou torna o código mais limpo e tem os efeitos colaterais de tornar mais difícil encontrar essas conversões implícitas em grandes projetos. Static_cast seria mais fácil pesquisar em todo o projeto do que essas construções.
Atul Kumar
3
@AtulKumar Como é mais fácil procurar static_cast do que pesquisar to_enum?
Johann Gerell 03/03/19
1
Esta resposta precisa de alguma explicação e documentação.
Lightness Races em Órbita
17

Não. Não existe um caminho natural .

De fato, uma das motivações por trás da digitação forte enum classdo C ++ 11 é impedir sua conversão silenciosa para int.

iammilind
fonte
Dê uma olhada na resposta de Khurshid Normuradov. Ele é o 'caminho natural' e é exatamente como pretendido em 'The C ++ Programming Language (4th ed.)'. Não vem de uma 'maneira automática', e isso é bom.
PapaAtHome
@PapaAtHome Eu não entendo os benefícios disso em static_cast. Não há muita alteração na digitação ou limpeza do código. Qual é o caminho natural aqui? Uma função retornando valor?
Atul Kumar
1
@ user2876962 O benefício, para mim, é que não é automático ou "silencioso", como o Iammilind coloca. Isso evita a dificuldade de encontrar erros. Você ainda pode fazer um elenco, mas é forçado a pensar nisso. Dessa forma, você sabe o que está fazendo. Para mim, isso faz parte de um hábito de 'codificação segura'. Prefiro que não sejam feitas conversões automáticas, existe uma chance de que isso possa causar um erro. Algumas mudanças no C ++ 11 relacionadas ao sistema de tipos se enquadram nessa categoria, se você me perguntar.
PapaAtHome 23/03
17

O motivo da ausência de conversão implícita (por design) foi dado em outras respostas.

Pessoalmente, uso unário operator+para a conversão de classes enum para seu tipo subjacente:

template <typename T>
constexpr auto operator+(T e) noexcept
    -> std::enable_if_t<std::is_enum<T>::value, std::underlying_type_t<T>>
{
    return static_cast<std::underlying_type_t<T>>(e);
}

O que fornece muito pouco "sobrecarga de digitação":

std::cout << foo(+b::B2) << std::endl;

Onde eu realmente uso uma macro para criar enums e o operador funciona de uma só vez.

#define UNSIGNED_ENUM_CLASS(name, ...) enum class name : unsigned { __VA_ARGS__ };\
inline constexpr unsigned operator+ (name const val) { return static_cast<unsigned>(val); }
Pixelchemist
fonte
13

Espero que isso ajude você ou outra pessoa

enum class EnumClass : int //set size for enum
{
    Zero, One, Two, Three, Four
};

union Union //This will allow us to convert
{
    EnumClass ec;
    int i;
};

int main()
{
using namespace std;

//convert from strongly typed enum to int

Union un2;
un2.ec = EnumClass::Three;

cout << "un2.i = " << un2.i << endl;

//convert from int to strongly typed enum
Union un;
un.i = 0; 

if(un.ec == EnumClass::Zero) cout << "True" << endl;

return 0;
}
vis.15
fonte
33
Isso é chamado de "punção de tipo" e, apesar de suportado por alguns compiladores, não é portátil, pois o padrão C ++ diz que após você definir un.iesse é o "membro ativo" e você só pode ler o membro ativo de uma união.
Jonathan Wakely
6
@ JonathanWakely Você está tecnicamente correto, mas nunca vi um compilador em que isso não funcione de maneira confiável. Coisas como essa, uniões anônimas e #pragma já foram padrões padrão.
BigSandwich
5
Por que usar algo que o padrão proíbe explicitamente quando um elenco simples fará? Isso está errado.
Paul Groke
1
Tecnicamente correto ou não, para mim é muito mais legível que outras soluções encontradas aqui. E o que é mais importante para mim, pode ser usado para resolver não apenas a serialização, mas também a desserialização da classe enum com facilidade e formato legível.
Marcin Waśniowski 21/03
6
Eu absolutamente me desespero que haja pessoas que considerem esse comportamento confuso e indefinido "muito mais legível" do que um simples static_cast.
underscore_d
13

Resposta curta é que você não pode, como as postagens acima apontam. Mas, no meu caso, eu simplesmente não queria desorganizar o espaço para nome, mas ainda tinha conversões implícitas, então fiz:

#include <iostream>

using namespace std;

namespace Foo {
   enum Foo { bar, baz };
}

int main() {
   cout << Foo::bar << endl; // 0
   cout << Foo::baz << endl; // 1
   return 0;
}

O namespacing meio que adiciona uma camada de segurança de tipo enquanto eu não tenho que converter estática nenhum valor de enumeração para o tipo subjacente.

solstice333
fonte
3
Não adiciona nenhum tipo de segurança (de fato, você acabou de remover o tipo de segurança) - apenas adiciona o escopo do nome.
Lightness Races em órbita
@LightnessRacesinOrbit sim, eu concordo. Eu menti. Tecnicamente, para ser exato, o tipo está logo abaixo de um espaço de nome / escopo e é totalmente qualificado para Foo::Foo. Os membros podem ser acessados ​​como Foo::bare Foo::baze podem ser implicitamente convertidos (e, portanto, não há muita segurança de tipo). Provavelmente é melhor usar quase sempre classes enum, especialmente se estiver iniciando um novo projeto.
solstice333
6

Isso parece impossível com o nativo enum class, mas provavelmente você pode zombar de um enum classcom um class:

Nesse caso,

enum class b
{
    B1,
    B2
};

seria equivalente a:

class b {
 private:
  int underlying;
 public:
  static constexpr int B1 = 0;
  static constexpr int B2 = 1;
  b(int v) : underlying(v) {}
  operator int() {
      return underlying;
  }
};

Isso é equivalente ao original enum class. Você pode retornar diretamente b::B1para uma função com o tipo de retorno b. Você pode fazer switch caseisso, etc.

E no espírito deste exemplo, você pode usar modelos (possivelmente junto com outras coisas) para generalizar e zombar de qualquer objeto possível definido pela enum classsintaxe.

Colliot
fonte
mas B1 e B2 devem ser definidos fora da classe ... ou isso não pode ser usado para case - header.h <- classe b - main.cpp <---- myvector.push_back (B1)
Fl0
Isso não deveria ser "static constexpr b" em vez de "static constexpr int '? Caso contrário, b :: B1 é apenas um int sem nenhuma segurança de tipo.
Some Guy
4

Como muitos disseram, não há como converter automaticamente sem adicionar sobrecargas e muita complexidade, mas você pode reduzir um pouco a digitação e melhorar a aparência usando lambdas se alguma conversão for usada um pouco demais em um cenário. Isso adicionaria um pouco de sobrecarga de função, mas tornará o código mais legível em comparação com as longas seqüências static_cast, como pode ser visto abaixo. Isso pode não ser útil em todo o projeto, mas apenas em toda a classe.

#include <bitset>
#include <vector>

enum class Flags { ......, Total };
std::bitset<static_cast<unsigned int>(Total)> MaskVar;
std::vector<Flags> NewFlags;

-----------
auto scui = [](Flags a){return static_cast<unsigned int>(a); };

for (auto const& it : NewFlags)
{
    switch (it)
    {
    case Flags::Horizontal:
        MaskVar.set(scui(Flags::Horizontal));
        MaskVar.reset(scui(Flags::Vertical)); break;
    case Flags::Vertical:
        MaskVar.set(scui(Flags::Vertical));
        MaskVar.reset(scui(Flags::Horizontal)); break;

   case Flags::LongText:
        MaskVar.set(scui(Flags::LongText));
        MaskVar.reset(scui(Flags::ShorTText)); break;
    case Flags::ShorTText:
        MaskVar.set(scui(Flags::ShorTText));
        MaskVar.reset(scui(Flags::LongText)); break;

    case Flags::ShowHeading:
        MaskVar.set(scui(Flags::ShowHeading));
        MaskVar.reset(scui(Flags::NoShowHeading)); break;
    case Flags::NoShowHeading:
        MaskVar.set(scui(Flags::NoShowHeading));
        MaskVar.reset(scui(Flags::ShowHeading)); break;

    default:
        break;
    }
}
Atul Kumar
fonte
2

O comitê C ++ deu um passo à frente (enumerações de escopo fora do espaço de nomes global) e cinquenta etapas atrás (nenhum tipo de enumeração decai para um número inteiro). Infelizmente, isso enum classsimplesmente não é utilizável se você precisar do valor da enum de qualquer maneira não simbólica.

A melhor solução é não usá-lo e, em vez disso, escopo a enum usando um espaço para nome ou uma estrutura. Para esse fim, eles são intercambiáveis. Você precisará digitar um pouco mais ao se referir ao tipo de enum, mas isso provavelmente não será frequente.

struct TextureUploadFormat {
    enum Type : uint32 {
        r,
        rg,
        rgb,
        rgba,
        __count
    };
};

// must use ::Type, which is the extra typing with this method; beats all the static_cast<>()
uint32 getFormatStride(TextureUploadFormat::Type format){
    const uint32 formatStride[TextureUploadFormat::__count] = {
        1,
        2,
        3,
        4
    };
    return formatStride[format]; // decays without complaint
}
Anne Quinn
fonte