Boost.Proto の式の戻り値型をユーザで定義する

『とりあえず、これで動いてる』版。これで合ってるのかな?
Boost.Proto の BOOST_PROTO_DEFINE_OPERATORS 等を使用しないで、ユーザコードで、operator + や、plus 関数なんかを定義する場合の式の戻り値型を考えます。
式の戻り値型は長いですが、そんなにややこしくないかな。

#include <boost/proto/proto.hpp>
#include <boost/mpl/inherit_linearly.hpp>
#include <iostream>

namespace proto = boost::proto;

//--------------------------------------
// 適当なベクトル型
struct vec{
    int value[3];
};

vec
make_vec(int x, int y, int z){
    vec v = {{x, y, z}};
    return v;
}

//--------------------------------------
// Boost.Proto
struct vec_context
    : proto::callable_context<const vec_context>{
    
    typedef int result_type;
    vec_context(int index) : index_(index){}
    
    result_type
    operator ()(proto::tag::terminal, vec const& v) const{
        return v.value[index_];
    }
    result_type
    operator ()(proto::tag::terminal, int const& n) const{
        return n;
    }
private:
    int index_;
};

// 演算を評価するための定義
struct grammar :
    proto::or_<
        proto::terminal<int>,
        proto::terminal<vec>,
        proto::plus<grammar, grammar>,
        proto::minus<grammar, grammar>,
        proto::multiplies<grammar, grammar>,
        proto::divides<grammar, grammar>
    >
{};

template<typename T>
struct vec_expr;

struct vec_domain :
    proto::domain<proto::generator<vec_expr>, grammar>{};

template<typename T>
struct vec_expr
    : proto::extends<T, vec_expr<T>, vec_domain>{
    explicit vec_expr(T const& t) : proto::extends<T, vec_expr<T>, vec_domain>(t){}
    
    // 一部の要素のみの評価を行う
    int operator [](std::size_t i) const{
        std::cout << "expression" << std::endl;
        return proto::eval(*this, vec_context(i) );
    }
    
    // vec 型にキャストする際に評価を行う
    operator vec() const{
        vec result;
        for(int i = 0 ; i < 3 ; ++i){
            result.value[i] = (*this)[i];
        }
        return result;
    }
};

// 演算の定義
template<typename>
struct is_vec : boost::mpl::false_{};
template<>
struct is_vec<vec> : boost::mpl::true_{};
BOOST_PROTO_DEFINE_OPERATORS(is_vec, vec_domain);
// ここまではお約束
//--------------------------------------

// ユーザで式を定義する
proto::result_of::make_expr<
    proto::tag::plus,
    vec_domain, vec const&, vec const&
>::type
plus(vec const& v1, vec const& v2){
    return proto::make_expr<proto::tag::plus, vec_domain>(boost::ref(v1), boost::ref(v2));
}

// expr も受け取れるように
template<typename T>
typename proto::result_of::make_expr<
    proto::tag::plus,
    vec_domain, vec_expr<T> const&, vec const&
>::type
plus(vec_expr<T> const& expr, vec const& v2){
    return proto::make_expr<proto::tag::plus, vec_domain>(boost::ref(expr), boost::ref(v2));
}


int
main(){
    vec v  = make_vec(10, 20, 30);
    vec v2 = make_vec(30, 20, 30);
    
    // ユーザ定義関数で処理する
    vec result = plus(plus(v, v2), v);
    
    std::cout << result.value[0] << std::endl;
    std::cout << result.value[1] << std::endl;
    std::cout << result.value[2] << std::endl;
    
    return 0;
}

[出力]

expression
expression
expression
50
60
90


こんな感じでしょうか。
proto::make_expr には、boost::ref で渡さないとダメみたいですね。
あと上記のコードでは、肥大化するので書いていませんが

vec result = plus(v, plus(v, v2));  // 第二引数に式を渡す

みたいな処理を行うと、plus に式を渡す時に型推論を行って無駄な評価が行われるので注意してくださいね。
ちゃんと、vec_expr 型を受け取れるように作りましょう。


[boost]
ver 1.46.1