エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

どれかと一致する

たくさん比較する必要があるときの、次のようなコードが好きでなくて。

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)と書けるようにしたのですが、ここでorC++予約語だと始めて知りました。へー。

別解

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)といった意味の塊に見えることを利用している。理解できるんですが、なんか不思議な感じ。