c ++列挙型クラスの要素数を決定することは可能ですか?


84

それは、C ++のカーディナリティを決定することが可能ですenum class

enum class Example { A, B, C, D, E };

使用しようとしましたsizeofが、列挙型要素のサイズが返されます。

sizeof(Example); // Returns 4 (on my architecture)

カーディナリティ(私の例では5)を取得する標準的な方法はありますか?


特定のc ++ 11メカニズムがあったのではないかと思いました
bquenin 2013

6
ちなみに、これは重複ではありません。enumenum classesは非常に異なる概念です。

@Shoe ...本当ですか?
カイルストランド

1
これはXYの問題のようです。昔からの問題だと思いますが、なぜこれを行う必要があったのか覚えていますか?enum class値を繰り返すことはできないので、数値を知ることのメリットは何でしょうか。
素晴らしいフォックス氏

回答:


70

直接ではありませんが、次のトリックを使用できます。

enum class Example { A, B, C, D, E, Count };

次に、カーディナリティはとして使用できますstatic_cast<int>(Example::Count)

もちろん、これは、列挙型の値を0から始めて自動的に割り当てる場合にのみうまく機能します。そうでない場合は、正しいカーディナリティーを手動でCountに割り当てることができます。これは、個別の定数を維持する必要があるのとまったく同じです。とにかく:

enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };

欠点の1つは、コンパイラーでExample::Count列挙値の引数として使用できることです。したがって、これを使用する場合は注意が必要です。(私は個人的にこれは実際には問題ではないと思います。)


1
列挙型の値は列挙型クラスでタイプセーフなので、「Count」はここではExample型であり、intではありません。サイズに使用するには、最初に「Count」をintにキャストする必要があります。
一方

@Man:はい、このトリックはenum classプレーンなenumsではなくesを使用すると少し面倒です。明確にするためにキャストで編集します。
キャメロン

11
この列挙型でswitchステートメントを使用すると、適切なコンパイラーは1つのケースが欠落していることを警告します。これを頻繁に使用すると、非常に煩わしい場合があります。この特定のケースでは、変数を分離する方がよい場合があります。
ファンタスティックミスターフォックス

@FantasticMrFox経験に基づいて、100%同意します。そのコンパイラの警告も重要です。私はあなたの推薦の精神にもっと一致する、別のアプローチを投稿しました。
arr_sea

26

C ++ 17の場合magic_enum::enum_countlibhttps//github.com/Neargye/magic_enumから使用できます

magic_enum::enum_count<Example>() -> 4。

欠点はどこにありますか?

このライブラリは、コンパイラ固有のハックを使用します(__PRETTY_FUNCTION__/に基づく__FUNCSIG__) Clang> = 5、MSVC> = 15.3、およびGCC> = 9で機能します。

指定された間隔範囲を調べて、名前の付いたすべての列挙を見つけます。これがそれらの数になります。制限についてもっと読む

この投稿https://taylorconor.com/blog/enum-reflectionで、このハックについてさらに詳しく説明します


2
これはすごい!列挙型メンバーの数を数えるために既存のコードを変更する必要はありません。また、これは非常にエレガントに実装されているようです(コードをざっと見ただけです)。
andreee

リンクのみの回答は一般的に推奨されていません。ライブラリが使用する手法の説明でこれを拡張できますか?
エイドリアンマッカーシー

24
constexpr auto TEST_START_LINE = __LINE__;
enum class TEST { // Subtract extra lines from TEST_SIZE if an entry takes more than one 
    ONE = 7
  , TWO = 6
  , THREE = 9
};
constexpr auto TEST_SIZE = __LINE__ - TEST_START_LINE - 3;

これはUglyCoderの回答から派生していますが、3つの方法で改善されています。

  • type_safe列挙型(BEGINおよびSIZE)には余分な要素はありません(Cameronの回答にもこの問題があります)。
    • コンパイラは、switchステートメントからそれらが欠落していることについて文句を言いません(重大な問題)
    • それらは、列挙型を期待する関数に誤って渡されることはありません。(一般的な問題ではありません)
  • 使用するために鋳造する必要はありません。(キャメロンの答えにもこの問題があります。)
  • 減算は、列挙型クラスタイプのサイズを混乱させません。

これは、列挙子に任意の値を割り当てることができるというCameronの回答に対するUglyCoderの利点を保持しています。

問題(UglyCoderと共有されているが、Cameronとは共有されていない)は、改行とコメントが重要になることです...これは予想外です。したがって、誰かがTEST_SIZE計算を調整せずに空白またはコメントを含むエントリを追加する可能性があります。


7
enum class TEST
{
    BEGIN = __LINE__
    , ONE
    , TWO
    , NUMBER = __LINE__ - BEGIN - 1
};

auto const TEST_SIZE = TEST::NUMBER;

// or this might be better 
constexpr int COUNTER(int val, int )
{
  return val;
}

constexpr int E_START{__COUNTER__};
enum class E
{
    ONE = COUNTER(90, __COUNTER__)  , TWO = COUNTER(1990, __COUNTER__)
};
template<typename T>
constexpr T E_SIZE = __COUNTER__ - E_START - 1;

賢い!もちろん、コメントや異常な間隔を設定することはできません。非常に大きなソースファイルの場合、基になる値の型がそうでない場合よりも大きくなる可能性があります。
カイルストランド2016

@Kyle Strand:その問題があります:charを使用すると、256を超える列挙子もあります。しかし、コンパイラは、切断型などを通知するためにマナーを持っているLINEは整数リテラルとの#lineを使用している[1、2147483647]の制限があります
UglyCoder

あ、そう。それでも、そうでなければaになる列挙型でさえ、たとえばユニティビルドを実行するときにshortぶつかる可能性がありますint。(ただし、これは、提案されたトリックよりもUnityビルドの方が問題だと思います。)
Kyle Strand

騙す?:-)私はそれを使用しますが、めったにそして正当な判断がありません。コーディングのすべてのように、私たちは賛否両論、特に長期的なメンテナンスの影響を取り除く必要があります。最近、これを使用して、C #defines(OpenGL wglExt.h)のリストから列挙型クラスを作成しました。
UglyCoder 2016

5

X()-マクロに基づく1つのトリックがあります:画像、次の列挙型があります:

enum MyEnum {BOX, RECT};

次のように再フォーマットします。

#define MyEnumDef \
    X(BOX), \
    X(RECT)

次に、次のコードで列挙型を定義します。

enum MyEnum
{
#define X(val) val
    MyEnumDef
#undef X
};

そして、次のコードは列挙型要素の数を計算します。

template <typename ... T> void null(T...) {}

template <typename ... T>
constexpr size_t countLength(T ... args)
{
    null(args...); //kill warnings
    return sizeof...(args);
}

constexpr size_t enumLength()
{
#define XValue(val) #val
    return countLength(MyEnumDef);
#undef XValue
}

...
std::array<int, enumLength()> some_arr; //enumLength() is compile-time
std::cout << enumLength() << std::endl; //result is: 2
...

これは、からコンマを削除する#define MyEnumDef(そしてそれを入れる)ことで簡単#define X(val) valにできます。これにより、#define X(val) +1 constexpr std::size_t len = MyEnumDef;。だけを使用して要素の数を数えることができます。
HolyBlackCat

4

試すことができる1つのトリックは、リストの最後に列挙値を追加し、それをサイズとして使用することです。あなたの例では

enum class Example { A, B, C, D, E, ExampleCount };

プレーンenumsの動作と比較すると、これはExampleCountタイプのようには機能しませんExample。内の要素の数を得るためにExampleExampleCount整数型にキャストされなければなりません。
applesoup 2018

3

boostのプリプロセッサユーティリティを使用する場合は、を使用してカウントを取得できますBOOST_PP_SEQ_SIZE(...)

たとえば、CREATE_ENUMマクロを次のように定義できます。

#include <boost/preprocessor.hpp>

#define ENUM_PRIMITIVE_TYPE std::int32_t

#define CREATE_ENUM(EnumType, enumValSeq)                                  \
enum class EnumType : ENUM_PRIMITIVE_TYPE                                  \
{                                                                          \
   BOOST_PP_SEQ_ENUM(enumValSeq)                                           \
};                                                                         \
static constexpr ENUM_PRIMITIVE_TYPE EnumType##Count =                     \
                 BOOST_PP_SEQ_SIZE(enumValSeq);                            \
// END MACRO   

次に、マクロを呼び出します。

CREATE_ENUM(Example, (A)(B)(C)(D)(E));

次のコードを生成します。

enum class Example : std::int32_t 
{
   A, B, C, D, E 
};
static constexpr std::int32_t ExampleCount = 5;

これは、ブーストプリプロセッサツールに関して表面を傷つけているだけです。たとえば、マクロで、強く型付けされた列挙型の文字列変換ユーティリティとostream演算子を定義することもできます。

Boostプリプロセッサツールの詳細については、https//www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.htmlをご覧ください。


余談ですが、私は@FantasticMrFoxに強く同意しCountます。受け入れられた回答で使用される追加の列挙値は、switchステートメントを使用する場合にコンパイラの警告の頭痛の種を大量に作成します。私が見つけunhandled case、私はそれを弱体化したくないので、より安全なコードの保守のためのコンパイラの警告は非常に便利。


@FantasticMrFox受け入れられた回答に関する懸念事項を指摘していただきありがとうございます。私はここで、あなたの推薦の精神にもっと一致する代替アプローチを提供しました。
arr_sea

2

std :: initializer_list:を使用したトリックで解決できます。

#define TypedEnum(Name, Type, ...)                                \
struct Name {                                                     \
    enum : Type{                                                  \
        __VA_ARGS__                                               \
    };                                                            \
    static inline const size_t count = []{                        \
        static Type __VA_ARGS__; return std::size({__VA_ARGS__}); \
    }();                                                          \
};

使用法:

#define Enum(Name, ...) TypedEnum(Name, int, _VA_ARGS_)
Enum(FakeEnum, A = 1, B = 0, C)

int main()
{
    std::cout << FakeEnum::A     << std::endl
              << FakeEnun::count << std::endl;
}

2

行数やテンプレートに依存しない別の方法があります。唯一の要件は、列挙値を独自のファイルに固定し、プリプロセッサ/コンパイラに次のようにカウントさせることです。

my_enum_inc.h

ENUMVAL(BANANA)
ENUMVAL(ORANGE=10)
ENUMVAL(KIWI)
...
#undef ENUMVAL

my_enum.h

typedef enum {
  #define ENUMVAL(TYPE) TYPE,
  #include "my_enum_inc.h"
} Fruits;

#define ENUMVAL(TYPE) +1
const size_t num_fruits =
  #include "my_enum_inc.h"
  ;

これにより、列挙値を使用してコメントを入力し、値を再割り当てでき、コードで無視/説明する必要のある無効な「カウント」列挙値を挿入できません。

コメントを気にしない場合は、追加のファイルは必要なく、上記の誰かのように行うことができます。例:

#define MY_ENUM_LIST \
    ENUMVAL(BANANA) \
    ENUMVAL(ORANGE = 7) \
    ENUMVAL(KIWI)

#include "my_enum_inc.h"ディレクティブをMY_ENUM_LISTに置き換えますが、#undef ENUMVAL使用するたびに置き換える必要があります。


1

これに対する別の種類の「愚かな」解決策は次のとおりです。

enum class Example { A, B, C, D, E };

constexpr int ExampleCount = [] {
  Example e{};
  int count = 0;
  switch (e) {
    case Example::A:
      count++;
    case Example::B:
      count++;
    case Example::C:
      count++;
    case Example::D:
      count++;
    case Example::E:
      count++;
  }

  return count;
}();

これをコンパイルすることによって -Werror=switchすること、switchケースを省略または複製した場合に、コンパイラの警告が表示されるようにしてください。これもconstexprであるため、これはコンパイル時に計算されます。

ただし、enenum classの場合でも、列挙型の最初の値が0でなくても、デフォルトの初期化値は0であることに注意してください。したがって、0から開始するか、最初の値を明示的に使用する必要があります。


0

いいえ、コードで記述する必要があります。


0

static_cast<int>(Example::E) + 1どちらが余分な要素を排除するかを検討することもできます。


8
この答えは、この特定のプログラミングの問題には正しいですが、一般的に、エレガントでエラーが発生しやすいとは言えません。列挙型は、将来Example::E、列挙型の最後の値として置き換えることができる新しい値で拡張できます。そうでない場合でも、Example::Eのリテラル値は変更される可能性があります。
マティアス

0

リフレクションTS:列挙型(およびその他のタイプ)の静的リフレクション

Reflection TS、特に最新バージョンのReflectionTSドラフトの[reflect.ops.enum] / 2は、次のget_enumerators TransformationTrait操作を提供します。

[reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

のすべての専門分野はget_enumerators<T>TransformationTrait要件(20.10.1)を満たしている必要があり ます。名前付きのネストされた型は 、をtype満たすメタオブジェクト型を指定し ObjectSequenceます。これにEnumeratorは、によって反映される列挙型の列挙子を満たし、反映する要素が含まれTます。

ドラフトの[reflect.ops.objseq]はObjectSequence操作をカバーし、特に[reflect.ops.objseq] / 1は、以下get_sizeを満たすメタオブジェクトの要素数を抽出するための特性をカバーしますObjectSequence

[reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

すべての専門は、get_size<T>満たさなければならない UnaryTypeTraitの塩基特性を有する要件(20.10.1) integral_constant<size_t, N>Nオブジェクトの配列内の要素の数であるが。

したがって、Reflection TSが受け入れられ、現在の形式で実装されることになっていた場合、列挙型の要素数は、コンパイル時に次のように計算できます。

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators<Example>::type;

static_assert(get_size<ExampleEnumerators>::value == 5U, "");

我々は、エイリアステンプレートを参照してくださいする可能性がある場所get_enumerators_vget_type_v、さらに反射を簡素化するために:

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators_t<Example>;

static_assert(get_size_v<ExampleEnumerators> == 5U, "");

リフレクションTSのステータス

ハーブサッターの旅行レポート: 2018年6月9日のISO C ++委員会の夏の会議からの夏のISOC ++標準会議(Rapperswil)で述べられているように、ReflectionTSは機能が完了していると宣言されています

Reflection TSは機能が完了しています:Reflection TSは機能が完了していると宣言され、夏の間、メインコメント投票のために送信されます。TSの現在のテンプレートメタプログラミングベースの構文は単なるプレースホルダーであることに再度注意してください。要求されているフィードバックは設計の核となる「根性」にあり、委員会は、表面の構文を、<>スタイルのメタプログラミングではなく通常のコンパイル時コードを使用するより単純なプログラミングモデルに置き換えることを意図していることをすでに知っています。

そして、された当初はC ++ 20のために予定が、反射TSはまだC ++ 20のリリースにそれを作るチャンスを持っているだろうかどうかはやや不明瞭です。

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