読者です 読者をやめる 読者になる 読者になる

Boost.Variant が特定の型の値を保持していた時だけ処理を実行する

boost::variant が保持している値の型が特定の型の場合だけ○○するという処理が多いの,でラムダ式で処理を書けるようにした. apply<{期待する variant が保持する値の型}>({variant な変数}, {適用したい関数}) という感じに使う.

#include <boost/variant/variant.hpp>
#include <iostream>

int main()
{
    boost::variant<int, double> v = 42;
    std::cout << *apply<int>(v, [](auto i){ return i * 3; }) << std::endl;    // 126
    std::cout << *apply<double>(v, [](auto i){ return i * 3; }) << std::endl; // do nothing
    apply<int>(v, [](auto i){ std::cout << i << std::endl; });                // 42
    apply<double>(v, [](auto i){ std::cout << i << std::endl; });             // do nothing

    return 0;
}

戻り値は boost::optional で,boost::variant が保持する値の型が apply() で指定した通りなら指定した関数を適用した戻り値を返し,そうでなければ boost::none を返す. Boost.Variant に型指定でアクセスしたいget() 関数テンプレートを使って次のように書ける.

#include <boost/optional.hpp>

template<class T, class Func, class RetType = decltype(std::declval<Func>()(std::declval<T>()))>
struct apply_impl{
    template<class Variant>
    static boost::optional<RetType> call(Variant const& v, Func const& f)
    {
        if(auto opt = get<T>(v)) {
            return f(*opt);
        } else {
            return boost::none;
        }
    }
};

template<class T, class Func>
struct apply_impl<T, Func, void>{
    template<class Variant>
    static bool call(Variant const& v, Func const& f)
    {
        if(auto opt = get<T>(v)) {
            f(*opt);
            return true;
        } else {
            return false;
        }
    }
};

template<class T, class Func, class Variant>
inline auto apply(Variant const& v, Func const& f)
    -> decltype(apply_impl<T, Func>::call(v, f))
{
    return apply_impl<T, Func>::call(v, f);
}

戻り値が void かどうかで分岐したかったので,実装にクラステンプレートを使っている.void の参照型は作れないので,boost::optional<void> が定義できないのが原因で,Boost.MPL の eval_if メタ関数で条件分岐しても良かったけれど,面倒そうなのでやめた. 指定した関数の戻り値型が void だったときの apply() の戻り値型は boost::optional<boost::none_t> が良かったけれど,定義できないので bool で妥協した.(たいていは auto で受けるので問題ないと思う)