web-dev-qa-db-ja.com

厳密に型指定された列挙型を自動的に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;
}

a::LOCAL_Aは厳密に型指定された列挙型が達成しようとしているものですが、わずかな違いがあります。通常の列挙型は整数型に変換できますが、厳密に型指定された列挙型はキャストなしではできません。

では、厳密に型指定された列挙値をキャストなしで整数型に変換する方法はありますか?はいの場合、どのように?

134
BЈовић

質問で言及した範囲の問題だけでなく、複数の問題を解決することを目的とした厳密に型指定された列挙型:

  1. 型の安全性を提供し、整数プロモーションによる暗黙的な整数への変換を排除します。
  2. 基になる型を指定します。
  3. 強力なスコープを提供します。

したがって、厳密に型指定された列挙型を整数に、またはその基礎となる型に暗黙的に変換することは不可能です-それがアイデアです。したがって、変換を明示的にするにはstatic_castを使用する必要があります。

あなたの唯一の問題がスコーピングであり、本当に整数に暗黙的に昇格させたい場合、宣言された構造のスコープで強く型付けされていない列挙型を使用する方が良いでしょう。

110
user405725

他の人が言ったように、あなたは暗黙の変換を持つことはできません、そしてそれは設計によるものです。

必要に応じて、キャストで基になる型を指定する必要を回避できます。

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

R。Martinho Fernandes が提供する回答のC++ 14バージョンは次のようになります。

#include <type_traits>

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

前の回答と同様に、これはあらゆる種類の列挙型および基礎となる型で機能します。 noexceptキーワードを追加したのは、例外をスローしないためです。


更新
これは、Effective Modern C++ by Scott Meyersにも表示されます。項目10を参照してください(本のコピー内の項目の最終ページで詳しく説明されています)。

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

いいえ。自然な方法なしがあります。

実際、C++ 11でenum classを厳密に型指定した背後にある動機の1つは、intへのサイレント変換を防ぐことです。

14
iammilind

これがあなたや誰かに役立つことを願っています

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

暗黙的な変換が(設計上)存在しない理由は、他の回答で説明されています。

私は個人的に単項operator+を使用して、enumクラスから基礎となる型への変換を行います。

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

「タイピングのオーバーヘッド」はほとんどありません。

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

実際にマクロを使用して列挙型と演算子関数を一度に作成します。

#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

上記の投稿が指摘しているように、簡単な答えはできません。しかし、私の場合、私は単に名前空間を乱雑にしたくはなかったが、それでも暗黙の変換を持っているので、私はただやった:

#include <iostream>

using namespace std;

namespace Foo {
   enum { bar, baz };
}

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

名前空間の並べ替えにより、型の安全性の層が追加されますが、列挙型の値を基になる型に静的にキャストする必要はありません。

9
solstice333

これはネイティブのenum classでは不可能に思えますが、おそらくenum classclassでモックできます:

この場合、

enum class b
{
    B1,
    B2
};

以下と同等です:

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

これは、元のenum classとほぼ同等です。戻り値の型bの関数でb::B1を直接返すことができます。それでswitch caseを行うことができます。

また、この例の精神では、テンプレートを(おそらく他のものと一緒に)使用して、enum class構文で定義された可能性のあるオブジェクトを一般化およびモックできます。

5
Colliot

多くの人が言ったように、オーバーヘッドと複雑さを増すことなく自動的に変換する方法はありませんが、シナリオでキャストが少し使用される場合、ラムダを使用することで入力を少し減らして見栄えを良くすることができます。これにより、関数のオーバーヘッド呼び出しが少し追加されますが、以下に示すように、長いstatic_cast文字列と比較してコードが読みやすくなります。これはプロジェクト全体では有用ではないかもしれませんが、クラス全体でのみ有効です。

#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