C++(14) Enumerateユーティリティ
Counter: 4470,
today: 5,
yesterday: 2
はじめに †C++AdventCalender2015 22日目の記事です。 21日目の記事は、Yuki Maria Miyatakeさんによるboost.Asioを半年使っわかったことです。 23日目の記事は、on_origami さんによるMATLABとC++の奇妙な関係 です。 Advent Calendarに参加するのも今年で4年目になりました。 痒いところに手がとどくようになってきたC++14ですが、便利になった反面、enumerateの書式は大きく進化しておらず、相対的に使い勝手が悪いと感じることが増えてきました。 そこで、以前から自前で作成していたenumerateツールをC++14対応にし、ブラッシュアップいたしましたので、ここで発表したいとおもいます。 12/25加筆 - return Pred::comp(toFlag(src.get<E>()), flags_); + return Pred::comp(toFlag(src.template get<E>()), flags_); もくじ †Chapter-1 enumの走査を便利にする †1-1 enumrateの走査 †国籍を表す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 enum class Nationality { Japanese, // 日本 American, // アメリカ German, // ドイツ French, // フランス Russian, // ロシア End, // end of Nationality }; 利用制限として、値が1ずつ増えることと、既存の定義に含まれない終端を定義することです 1-2 EnumListの定義 †// 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)という書式で列挙対を走査することがでるようになりました。 Chapter-2 enumをフラグとして使いたい †2-1 従来の方法の問題点 †フラグの定義はいにしえの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++でこんなコードは書きたくないです! 2-2 EnumListを利用したenumerateのフラグ化 †先ほどの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); // ドイツ、フランス、ロシアが実行される }); 2-3 EnumList::Flagsの実装 †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)); } } Chapter-3 enumをboost::adaptorsから使いたい †EnumListでenumの操作が楽になりましたが、現実的にはenum変数の定義は構造体の内部にあり、構造体と一緒に扱われることが多いでしょう。 3-1 boost adaptorsで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を使用するとちょっとカッコイイですね。 でも、冗長さはあまり変わりません。 3-2 EnumAdapter †そこで、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_ をどうやって見つけているのか、不思議になるとおもいます。 3-3 構造体のメンバ変数を型から取得するためのgetter定義 †種明かしをすると、以下の一行が追加されています 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<型>() からクラス構造体のメンバ変数にアクセスできる、とても便利な関数です。 3-4 EnumAdapterの実装 †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.template 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ライクな感じで好きですが、手軽に使うにはまだまだ知識が追い付かず調べてからでないと使い方が分かりません。 |