C++(14) Enumerateユーティリティ
C++AdventCalender2015 22日目の記事です。
21日目の記事は、Yuki Maria Miyatakeさんによるboost.Asioを半年使っわかったことです。
23日目の記事は、on_origami さんによる MATLABとC++の奇妙な関係 です。
Advent Calendarに参加するのも今年で4年目になりました。
ゲームプログラミングにC++11を導入して約2年半あまり。最近はC++14でコーディングするようになり、シンタックスシュガー漬けの甘い日々を過ごしています。
痒いところに手がとどくようになってきたC++14ですが、便利になった反面、enumerateの書式は大きく進化しておらず、相対的に使い勝手が悪いと感じることが増えてきました。
そこで、以前から自前で作成していたenumerateツールをC++14対応にし、ブラッシュアップいたしましたので、ここで発表したいとおもいます。
国籍を表すenumerate定義
enum class Nationality { Japanese, // 日本 American, // アメリカ German, // ドイツ French, // フランス Russian, // ロシア };
すべてのNationalityを走査するには?
for (int n = 0; n <= static_cast<int>(Nationality:: Russian); ++n) { auto nationality = static_cast<Nationality>(n); hoge(nationality); // Natioanlityを使用した処理 }
static_castが冗長でイヤですね
こんな風に書けたら便利です
NationalityList::eachEnum([](auto nationality) { hoge(nationality); // Natioanlityを使用した処理 });
EnumListというヘルパークラスを作りました
using NationalityList = EnumList<Nationality, Nationality::End>;
これでOK
終端を定義するために、列挙体の最後に'End'を追加してあります。
enum class Nationality { Japanese, // 日本 American, // アメリカ German, // ドイツ French, // フランス Russian, // ロシア End, // end of Nationality };
利用制限として、値が1ずつ増えることと、既存の定義に含まれない終端を定義することです
// enumの定義をフラグ化およびスキャンするユーティリティ template <typename E, E Last, int FirstEnum = 0> struct EnumList { using value_type = typename std::underlying_type<E>::type; static const int First = FirstEnum; static const int Count = static_cast<int>(Last) - First; // enumを[First,Last)でなめる template <typename Proc> static void eachEnum(Proc proc) { for (value_type n = First; n < static_cast<value_type>(Last); ++n) { proc(static_cast<E>(n)); } }
これで、EnumList<First,Last>::eachEnum(Proc)という書式で列挙対を走査することがでるようになりました。
フラグの定義はいにしえのCの時代から大きく変化していません。
#defineをenumもしくはconst intに置き換えた程度で、使い勝手はむしろ#defineのマクロを駆使したほうが良いぐらいです。
国情報のフラグ化の例
enum class NationalityBitFlags { Japanese = 1 << 0, // 日本 American = 1 << 1, // アメリカ German = 1 << 2, // ドイツ French = 1 << 3, // フランス Russian = 1 << 4, // ロシア };
国籍のフラグに日本が含まれているか否かをチェックする関数はこうなります
bool isJapanese(NationalityBitFlags n) { return (static_cast<int>(n) & static_cast<int>(NationalityBitFlags::Japanese)) != 0; }
static_castが冗長でイヤですね
さらに条件が少し複雑(ヨーロッパ限定)になると、
bool isEuropean(Nationality n) { return (static_cast<int>(n) & ( static_cast<int>(Nationality::German)| static_cast<int>(Nationality::French)| static_cast<int>(Nationality::Russian)) != 0; }
とてもイヤな感じですね
いまどきのC++でこんなコードは書きたくないです!
先ほどのEnumListクラスにフラグを扱うクラスを追加して、以下のように記述できるようにしました
using NationalityFlags = EnumList<Nationality, Nationality::End>::Flags // 日本人か? bool isJapanese(Nationality n) { return NationalityFlags(n)(Nationality::Japanese); } // ヨーロッパ人か? bool isEuropean(Nationality n) { return NationalityFlags(n)(Nationality::German | Nationality::French | Nationality::Russian); }
スッキリしました。enumの値を|で結合して簡単に比較ができます。
フラグがセットされているものだけ走査することもできます
NationalityFlags euro(Nationality::German | Nationality::French | Nationality::Russian) euro.eachEnum([](auto nationality) { hoge(nationality); // ドイツ、フランス、ロシアが実行される });
EnumListにFlagsというクラスを追加しました
// enumの定義をフラグ化およびスキャンするユーティリティ template <typename E, E Last, int FirstEnum = 0> // , typename Type = int> struct EnumList { using value_type = typename std::underlying_type<E>::type; static const int First = FirstEnum; static const int Count = static_cast<int>(Last) - First; // Enumをフラグとして扱うクラス。(32個もしくは64個以内) template <typename IntT> struct FlagsT { IntT flags_ = 0; FlagsT() : flags_(0) {} FlagsT(IntT flags) : flags_(flags) {} FlagsT(E value) { set(value); } // 初期化(すべてオフ) void clear() { flags_ = 0; } // フラグセット void set(E value) { flags_ |= static_cast<IntT>(1) << static_cast<int>(value); } void reset(E value) { flags_ &= ~(static_cast<IntT>(1) << static_cast<int>(value)); } // セットされているフラグの数を返す uint32_t count() const { uint32_t count = 0; for (value_type n = First; n < static_cast<value_type>(Last); ++n) { if ((flags_ & (1 << n)) != 0) ++count; } return count; } // check bool operator() (E value) const { return (flags_ & (static_cast<IntT>(1) << static_cast<int>(value))) != 0; } // フラグの連結 FlagsT& operator | (E value) { set(value); return *this; } // フラグがOnのenumをなめる template <typename Proc> void eachEnum(Proc proc) const { for (value_type n = First; n < static_cast<value_type>(Last); ++n) { if ((flags_ & (1 << n)) != 0) { proc(static_cast<E>(n)); } } } }; using Flags = FlagsT<uint32_t>; using Flags64 = FlagsT<uint64_t>; // enumを[First,Last)でなめる template <typename Proc> static void eachEnum(Proc proc) { for (value_type n = First; n < static_cast<value_type>(Last); ++n) { proc(static_cast<E>(n)); } }
EnumListでenumの操作が楽になりましたが、現実的にはenum変数の定義は構造体の内部にあり、構造体と一緒に扱われることが多いでしょう。
クラスオブジェクトのリストを走査する場合に、enum変数を値によって振り分けるといったことが良くあります。
名前と国籍から構成されるクラスとそのリストがあります
struct Human { string name_; // 名前 Nationality nationality_; // 国籍 }; vector<Human> people; // Humanのリスト
boost::adaptorsを使用しない場合、peopleにある日本国籍のHumanのみ処理する場合、こうなります
for (auto& human : people) { if (human.nationality_ != Nationality::Japanese) continue; hoge(human); /// 何らかの処理 };
boost::adaptors::filteredを使うとこうなります
using boost::adaptors::filtered; for (auto& human : people | filtered([](auto& human) { return human.nationality_ == Nationality::Japanese; })) { hoge(human); /// 何らかの処理 };
filteredを使用するとちょっとカッコイイですね。 でも、冗長さはあまり変わりません。
そこで、boost::adaptoersでenumを扱うためのヘルパークラス EnumAdapter を作りました こんな具合に書けます
using boost::adaptors::filtered; for (auto& human : people | filtered(+Nationality::Japanese)) { hoge(human); /// 何らかの処理 };
スッキリしましたね。 Nationality列挙体の単項演算子'+'がミソです。
もうおわかりだと思いますが、こんな風にも書けます。
// 特定条件での走査 (日本人とフランス人を対象とする) for (auto& h : people | filtered(Nationality::Japanese | Nationality::French)){ hoge(human); /// 何らかの処理 }
サッパリしてますね。以下の従来の書き方と比べてみてください
// 特定条件での走査 (日本人とフランス人を対象とする) for (auto& h : people | filtered([](auto& h) { return h.nationality_ == Nationality::Japanese || h.nationality_ == Nationality::French; })){ hoge(human); /// 何らかの処理 }
よく見ると、filterd(+Nationality::Japanese) が、Human構造体の要素である nationality_ をどうやって見つけているのか、不思議になるとおもいます。
もちろん、これには種も仕掛けもございます。
種明かしをすると、以下の一行が追加されています
struct Human { string name_; // 名前 Nationality nationality_; // 国籍 FSG_MAKE_GETTER(name_, nationality_); };
悪魔のマクロ定義ですね
まだ悪魔に魂を売り渡したくない人は、以下のように記述してください
struct Human { string name_; // 名前 Nationality nationality_; // 国籍 template<typename T> const T& get() const { return *boost::fusion::find<const T&>(boost::fusion::vector_tie(name_, nationality_)); } };
このgetterは、get<型>() からクラス構造体のメンバ変数にアクセスできる、とても便利な関数です。
EnumAdapterの実装です
// Enumlateをorで結合し、条件判定を行うためのアダプタクラス template <typename E, typename Pred, typename FlagT = uint32_t> struct EnumPredAdapter { using value_type = FlagT; value_type flags_ = 0; // constructor EnumPredAdapter(value_type v) : flags_(v) {} EnumPredAdapter(E a) : flags_(toFlag(a)) {} EnumPredAdapter(E a, E b) : flags_(toFlag(a) | toFlag(b)) {} // enum値からフラグにする static value_type toFlag(E value) { return 1 << static_cast<value_type>(value); } // operator EnumPredAdapter operator | (E a) { return flags_ | toFlag(a); } EnumPredAdapter operator | (EnumPredAdapter a) { return flags_ | a.flags_; } EnumPredAdapter operator & (EnumPredAdapter a) { return flags_ & a.flags_; } template<typename T> bool operator == (const T& src) const { return this->operator&() (src); } template<typename T> bool operator () (const T& src) const { return Pred::comp(toFlag(src.get<E>()), flags_); } }; struct EnumPredAny { template<typename VT> static bool comp(VT a, VT b) { return (a & b) != 0; } }; struct EnumPredNot { template<typename VT> static bool comp(VT a, VT b) { return (a & b) == 0; } }; template <typename T, typename E, typename Pred, typename VT = int> inline bool operator == (const T& src, EnumPredAdapter<E, Pred, VT> v) { return v(src); } template <typename T, typename E, typename Pred, typename VT = int> inline bool operator != (const T& src, EnumPredAdapter<E, Pred, VT> v) { return !v(src); } } // 引数で指定したT型の変数をget<T>()で取得するためのマクロ // eg. // struct { int a; float b, string c; FSG_MAKE_GETTER(a,b,c) }; // get<int>()でa、get<float>()でb、get<string>()でcが取得できる #define FSG_MAKE_GETTER(...) \ template<typename T> const T& get() const {\ return *boost::fusion::find<const T&>(boost::fusion::vector_tie(__VA_ARGS__)); }
長い文章を最後まで読んでいただいてありがとうございました。
簡単なクラスですが、enum変数の操作をスッキリかけるようになりました。boost::adaptoresの処理は、C#のLinqライクな感じで好きですが、手軽に使うにはまだまだ知識が追い付かず調べてからでないと使い方が分かりません。
今回も、boost::fusionの使い方について、高橋晶さんに助けていただきました。ありがとうございます。
fusionもとても便利なクラスなので、機会があればどんどん使っていきたいとおもいます。