強く型付けされた列挙型を自動的にintに変換する方法は?


164
#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

あなたの質問で述べたようにスコープの問題だけでなく、複数の問題を解決することを目的とした強く型付けされた列挙型:

  1. 型の安全性を提供し、整数の昇格による整数への暗黙の変換を排除します。
  2. 基になるタイプを指定します。
  3. 強力なスコープを提供します。

したがって、強く型付けされた列挙型を暗黙的に整数に変換したり、その基礎となる型に変換したりすることは不可能です。したがってstatic_cast、変換を明示的にするために使用する必要があります。

唯一の問題がスコープであり、整数への暗黙的な昇格が本当に必要な場合は、強く型付けされていない列挙型を、それが宣言されている構造体のスコープで使用することをお勧めします。


2
これは、C ++クリエーターからの「あなたが何をしたいかをよく知っている」というもう1つの奇妙な例です。従来の(古いスタイルの)列挙型には、インデックスへの暗黙的な変換、ビット単位の演算のシームレスな使用など、多くの利点がありました。新しいスタイルの列挙型は、非常に優れたスコープを追加しましたが、...基礎となる型仕様!)したがって、古いスタイルの列挙型を構造体に入れるなどのトリックを使用するか、またはstd :: vectorの周りに独自のラッパーを作成するなどの新しい列挙型の醜い回避策を作成して、CASTの問題を克服する必要があります。コメントなし
avtomaton

152

他の人が言ったように、暗黙の変換を行うことはできません。これは仕様によるものです。

必要な場合は、キャストで基になる型を指定する必要を回避できます。

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;

75

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例外をスローしないため、キーワードを追加しました。


アップデート
これは、Scott MeyersによるEffective Modern C ++にも表示されます。項目10を参照してください(これは、私の本のコピー内の項目の最後のページで詳しく説明されています)。


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

3
これにより、入力が減ったりコードがすっきりしたりせず、大きなプロジェクトでこのような暗黙の変換を見つけることが難しくなるという副作用があります。Static_castは、これらの構成よりプロジェクト全体を検索する方が簡単です。
Atul Kumar

3
@AtulKumar to_enumを検索するよりもstatic_castを検索する方が簡単ですか。
Johann Gerell

1
この回答には、いくつかの説明とドキュメントが必要です。
、オービットでの軽量レース

17

いいえ、自然な方法はありません

実際、enum classC ++ 11で強く型付けされた動機の1つは、C ++ 11へのサイレント変換を防ぐことintです。


Khurshid Normuradovからの返信を見てください。これは「自然な方法」であり、「C ++プログラミング言語(第4版)」で意図されているとおりです。それは「自動的な方法」で来ません、そしてそれはそれについて良いです。
PapaAtHome 2015年

@PapaAtHome static_castと比較した場合の利点がわかりません。タイピングやコードのクリーン度に大きな変化はありません。ここで自然な方法は何ですか?値を返す関数?
Atul Kumar

1
@ user2876962私にとっての利点は、Iammilindが言うように、自動的でも「サイレント」でもないことです。これにより、エラーを見つけることが困難になります。あなたはまだキャストを行うことができますが、それについて考えることを余儀なくされます。そうすれば、自分が何をしているかがわかります。私にとってそれは「安全なコーディング」の習慣の一部です。エラーが発生する可能性がある場合は、変換が自動的に行われないようにすることをお勧めします。型システムに関連するC ++ 11のかなりの変更は、私に尋ねると、このカテゴリに該当します。
PapaAtHome 2015年

17

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

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

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;

マクロを実際に使用して列挙型と演算子関数を1つのショットで作成する場合。

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

13

これがあなたや誰かを助けることを願っています

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

33
これは「タイプパニング」と呼ばれ、一部のコンパイラではサポートされていませんが、C ++標準un.iでは、設定後は「アクティブメンバー」であり、共用体のアクティブメンバーしか読み取ることができないと記載されています。
Jonathan Wakely、2014年

6
@JonathanWakelyあなたは技術的に正しいですが、これが確実に機能しないコンパイラを見たことがありません。このようなもの、匿名組合、および#pragmaは、事実上の標準です。
BigSandwich 2015年

5
単純なキャストが行うのに、なぜ標準が明示的に禁止しているものを使用するのですか?これは間違っています。
Paul Groke

1
技術的に正しいかどうか、私にとっては、ここにある他のソリューションよりも読みやすいです。そして、私にとってより重要なことは、シリアル化だけでなく、列挙型クラスの逆シリアル化を簡単に解決し、読みやすいフォーマットを使用することです。
MarcinWaśniowski2017年

6
この厄介な未定義の動作を、単純なものよりも「読みやすく」考えている人がいることは絶対に残念ですstatic_cast
underscore_d

13

上記の投稿が指摘するように、短い答えはあなたがすることはできません。しかし、私の場合、名前空間を乱雑にしたくなかっただけで、暗黙の変換がまだ残っているので、次のようにしました。

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

名前空間の一種は、タイプセーフのレイヤーを追加しますが、列挙型の値を基になる型に静的にキャストする必要はありません。


3
タイプセーフはまったく追加されません(実際、タイプセーフが削除されただけです)。名前のスコープが追加されるだけです。
、オービットでの軽量レース

@LightnessRacesinOrbitはい、同意します。私は嘘をついた。技術的には、正確には、型は名前空間/スコープのすぐ下にあり、完全に修飾されFoo::Fooます。メンバーはFoo::barおよびとしてアクセスFoo::bazでき、暗黙的にキャストできます(型の安全性はそれほど高くありません)。特に新しいプロジェクトを開始する場合は、ほとんど常に列挙型クラスを使用する方が良いでしょう。
solstice333

6

これは、ネイティブで不可能だ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::B1戻り値の型を持つ関数で直接forに戻ることができbます。できるよswitch case等で。

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


しかし、B1とB2はクラスの外部で定義する必要があります...または、これはケースでは使用できません-header.h <-class b-main.cpp <---- myvector.push_back(B1)
Fl0

それは「静的constexpr int」の代わりに「静的constexpr b」である必要はありませんか?それ以外の場合、b :: B1は型保証のない単なるintです
Some Guy

4

多くの人が言ったように、オーバーヘッドと複雑さを追加せずに自動的に変換する方法はありませんが、いくつかのキャストがシナリオで少し使用される場合は、入力を少し減らし、ラムダを使用して見栄えをよくすることができます。これにより、関数のオーバーヘッド呼び出しが少し追加されますが、以下に示すように、長い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;
    }
}

2

C ++委員会は1歩進んで(グローバルネームスペースから列挙型のスコープを取得)、50段階後退しました(列挙型の整数への減衰はありません)。悲しいことにenum class列挙型の値が非記号的な方法で必要な場合は、単に使用できません。

最善の解決策は、それをまったく使用せず、代わりにネームスペースまたは構造体を使用して列挙型を自分でスコープすることです。この目的のために、それらは交換可能です。列挙型自体を参照するときは、少し余分に入力する必要がありますが、多くの場合はそうではありません。

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
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.