たくさん比較する必要があるときの、次のようなコードが好きでなくて。
enum Month { Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }; void foo(Month month) { if( (month == Jan) || (month == Mar) || (month == May) || (month == Jul) || (month == Aug) || (month == Oct) || (month == Dec) ) { std::cout << "大の月" << std::endl; } else { std::cout << "小の月" << std::endl; } }
こういうときは条件式を別の関数にするのがよいのですが、あえて別解に挑む。
template<typename T> class EitherObj { public: EitherObj(const EitherObj* prev, T value) : prev_(prev), value_(value) {} EitherObj operator () (T value) { return EitherObj(this, value); } bool eval(T value) const { if(value == value_) { return true; } else if(prev_ == 0) { return false; } else { return prev_->eval(value); } } private: const EitherObj* prev_; const T value_; }; template<typename T> EitherObj<T> either(T value) { return EitherObj<T>(0, value); } template<typename T> bool operator == (T lhs, const EitherObj<T>& rhs) { return rhs.eval(lhs); }
これで最初のコードは次のように。
void foo(Month month) { if(month == either(Jan)(Mar)(May)(Jul)(Aug)(Oct)(Dec)) { std::cout << "大の月" << std::endl; } else { std::cout << "小の月" << std::endl; } }
各値を持った一時オブジェクトをリンクトリストにして、リストの端の値から順に比較していくというもの。リストを前に前に伸ばしていくので、最初の要素がリストの最後にくるため、比較の順序は書いてある順序と逆になってます(x == either(A)(B)(C)
は(x == C) || (x == B) || (x == A)
となります)。
括弧を書けば書くほど一時オブジェクトが増え、呼び出す関数が深くなる。実行コストと読みやすさのどちらを取るかという問題。読みやすくもないという話もありますが。
ちなみに。最初operator ()
の代わりにOr
という関数を用意して、either(A).Or(B).Or(C)
と書けるようにしたのですが、ここでor
がC++の予約語だと始めて知りました。へー。
別解
id:wraith13さんにコメント頂いた方法で実装してみました。
template<typename T> class Hoge { public: explicit Hoge(T value) : value_(value) {} class Result { public: Result(bool result, T value) : result_(result), value_(value) {} operator bool () const { return result_; } Result& operator , (T value) { result_ = result_ || (value_ == value); return *this; } private: bool result_; T value_; }; Result operator == (T value) { return Result(value_ == value, value_); } private: T value_; }; template<typename T> Hoge<T> hoge(T value) { return Hoge<T>(value); }
改めて、使い方。
void foo(Month month) { if(hoge(month) == Jan, Mar, May, Jul, Aug, Oct, Dec) { std::cout << "大の月" << std::endl; } else { std::cout << "小の月" << std::endl; } }
ほんとですね。こちらの方がコストが安いですし、比較してコードもわかりやすい…かな?わかりにくいとしたら、わたしの実装が悪いということで。
ところで。改めて考えてみれば、x == A,B,C
というコードは、(((x == A),B),C)
という意味なんですよね。それがx == (A,B,C)
といった意味の塊に見えることを利用している。理解できるんですが、なんか不思議な感じ。