web-dev-qa-db-fra.com

Comment convertir automatiquement un enum fortement typé en int?

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

Le a::LOCAL_A correspond à ce que l'énumération fortement typée essaie de réaliser, mais il existe une petite différence: les énumérations normales peuvent être converties en type entier, alors que les énumérations fortement typées ne peuvent le faire sans conversion.

Alors, est-il possible de convertir une valeur enum fortement typée en un type entier sans transtypage? Si oui comment?

138
BЈовић

Enums fortement typés visant à résoudre de multiples problèmes et pas seulement le problème de cadrage comme vous l'avez mentionné dans votre question:

  1. Assure la sécurité du type, éliminant ainsi la conversion implicite en entier par promotion intégrale.
  2. Spécifiez les types sous-jacents.
  3. Fournir une forte portée.

Ainsi, il est impossible de convertir implicitement une énumération fortement typée en entiers, ou même son type sous-jacent - c'est l'idée. Vous devez donc utiliser static_cast pour rendre la conversion explicite.

Si votre seul problème est la portée et que vous voulez vraiment avoir une promotion implicite sur les entiers, il vaut mieux utiliser une énumération non typée avec l'étendue de la structure dans laquelle elle est déclarée.

114
user405725

Comme d'autres l'ont dit, vous ne pouvez pas avoir de conversion implicite, et c'est une conception.

Si vous le souhaitez, vous pouvez éviter de spécifier le type sous-jacent dans la distribution.

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;
135

Une version C++ 14 de la réponse fournie par R. Martinho Fernandes serait:

#include <type_traits>

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

Comme avec la réponse précédente, cela fonctionnera avec n'importe quel type d'énum et de type sous-jacent. J'ai ajouté le mot clé noexcept car il ne lève jamais d'exception.


Mettre à jour
Ceci apparaît également dans Effective Modern C++ de Scott Meyers . Voir le point 10 (il est détaillé dans les dernières pages du point dans ma copie du livre).

61
Class Skeleton
#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;
}
18

Non, il y a pas de manière naturelle.

En fait, une des motivations derrière avoir fortement typé enum class en C++ 11 est d'empêcher leur conversion silencieuse en int.

15
iammilind

J'espère que cela vous aide ou aide quelqu'un d'autre

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;
}
12
vis.15

La raison de l'absence de conversion implicite (par conception) a été donnée dans d'autres réponses.

J'utilise personnellement unary operator+ pour la conversion de classes enum en leur type sous-jacent:

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

Ce qui donne assez peu de "frais généraux de frappe":

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

Où j'utilise réellement une macro pour créer des énumérations et les fonctions d'opérateur en une seule fois.

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

La réponse courte est que vous ne pouvez pas, comme indiqué ci-dessus. Mais dans mon cas, je ne voulais tout simplement pas encombrer l’espace de noms, mais toujours avoir des conversions implicites.

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

La sorte d'espacement de noms ajoute une couche de sécurité de type alors que je n'ai pas besoin de convertir statiquement des valeurs enum sur le type sous-jacent.

9
solstice333

Cela semble impossible avec le enum class natif, mais vous pouvez probablement vous moquer d'un enum class avec un class:

Dans ce cas,

enum class b
{
    B1,
    B2
};

serait équivalent à:

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

Ceci est essentiellement équivalent à l'original enum class. Vous pouvez directement renvoyer b::B1 dans une fonction de type de retour b. Vous pouvez faire switch case avec, etc.

Et dans l'esprit de cet exemple, vous pouvez utiliser des modèles (éventuellement avec d'autres éléments) pour généraliser et simuler tout objet possible défini par la syntaxe enum class.

5
Colliot

Comme beaucoup l'ont dit, il n'y a aucun moyen de convertir automatiquement sans ajouter des frais généraux et trop de complexité, mais vous pouvez réduire votre frappe et la rendre plus belle en utilisant lambdas si certains acteurs sont utilisés un peu trop dans un scénario. Cela ajouterait un peu d'appel de surcharge de fonction, mais rendrait le code plus lisible par rapport aux longues chaînes static_cast, comme on peut le voir ci-dessous. Cela peut ne pas être utile à l’échelle du projet, mais seulement à l’échelle de la 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;
    }
}
4
Atul Kumar