I'm experimenting with Proto to build a DSEL that operates on geometric vectors. I'm trying to write a transform that would take an assign expression and unroll it component wise. For instance, I want to replace
p = q + r;
by
p[0] = q[0] + r[0],
p[1] = q[1] + r[1],
...,
p[N] = q[N] + r[N],
p;
So far I have been able to mostly make it work by making a transform template unroll_vector_expr
that recursively unrolls the expression for each vector component, in conjunction with a distribute_subscript
transform.
I don't seem to understand whats the rationale for the 'parameters' used with boost::result_of
and result_of::make_expr
. The documentation, examples and internal code seem to mix Expr
, impl::expr
and impl::expr_param
. I'm not sure what should I be using and when so that the result_type
matches the actual result type. Currently I made it work by trial an error, by examining the error messages and fixing the const
and &
discrepancies. However, as soon as I deep copy the expression the terminals are no longer hold by reference and my code fails.
struct distribute_subscript
: or_<
scalar_grammar
, when<
vector_literal
, _make_subscript( _, _state )
>
, plus< distribute_subscript, distribute_subscript >
, minus< distribute_subscript, distribute_subscript >
, multiplies< distribute_subscript, distribute_subscript >
, divides< distribute_subscript, distribute_subscript >
, assign< distribute_subscript, distribute_subscript >
>
{};
template< std::size_t I, std::size_t N >
struct unroll_vector_expr_c;
template< std::size_t I, std::size_t N >
struct unroll_vector_expr_c
: transform< unroll_vector_expr_c< I, N > >
{
template< typename Expr, typename State, typename Data >
struct impl
: transform_impl< Expr, State, Data >
{
typedef
typename result_of::make_expr<
tag::comma
, typename boost::result_of<
distribute_subscript(
Expr
, typename result_of::make_expr<
tag::terminal
, boost::mpl::size_t< I - 1 >
>::type
)
>::type
, typename boost::result_of<
unroll_vector_expr_c< I + 1, N >(
Expr
)
>::type
>::type
result_type;
result_type operator ()(
typename impl::expr_param expr
, typename impl::state_param state
, typename impl::data_param data
) const
{
return
make_expr< tag::comma >(
distribute_subscript()(
expr
, make_expr< tag::terminal >(
boost::mpl::size_t< I - 1 >()
)
)
, unroll_vector_expr_c< I + 1, N >() (
expr
)
);
}
};
};
template< std::size_t N >
struct unroll_vector_expr_c< N, N >
: transform< unroll_vector_expr_c< N, N > >
{
template< typename Expr, typename State, typename Data >
struct impl
: transform_impl< Expr, State, Data >
{
typedef
typename boost::result_of<
distribute_subscript(
Expr
, typename result_of::make_expr<
tag::terminal
, boost::mpl::size_t< N - 1 >
>::type
)
>::type
result_type;
result_type operator ()(
typename impl::expr_param expr
, typename impl::state_param state
, typename impl::data_param data
) const
{
return
distribute_subscript()(
expr
, make_expr< tag::terminal >(
boost::mpl::size_t< N - 1 >()
)
);
}
};
};
struct unroll_vector_expr
: transform< unroll_vector_expr >
{
template< typename Expr, typename State, typename Data >
struct impl
: transform_impl< Expr, State, Data >
{
typedef
typename dimension<
typename boost::remove_reference<
typename boost::result_of<
_value( State )
>::type
>::type
>::type
dimension;
typedef
typename result_of::make_expr<
tag::comma
, typename boost::result_of<
unroll_vector_expr_c< 1, dimension::value >(
Expr
)
>::type
, State
>::type
result_type;
result_type operator ()(
typename impl::expr_param expr
, typename impl::state_param state
, typename impl::data_param data
) const
{
return
make_expr< tag::comma >(
unroll_vector_expr_c< 1, dimension::value >()(
expr
)
, boost::ref( state )
);
}
};
};
How should I write my transform so that the result_type
matches the result from operator ()
, and works for terminal
s both hold by value and by reference?
Update: Code updated after Eric's answer. The only remaining mismatch between result_type
and make_expr
is for the first instantiation of unroll_vector_expr_c
, where the terminal< mpl::size_t< 0 > >::type
is held by const reference instead of by value. Curiously enough, subsequent instantiations of the same template with higher indices do not result in this issue.
Update: I managed to get the code to fully work, after modifying the distribue_subscript
transform to force to take the subscript index by value:
struct distribute_subscript
: or_<
scalar_grammar
, when<
vector_literal
, _make_subscript( _, _byval( _state ) ) // <-- HERE
>
, plus< distribute_subscript, distribute_subscript >
, minus< distribute_subscript, distribute_subscript >
, multiplies< distribute_subscript, distribute_subscript >
, divides< distribute_subscript, distribute_subscript >
, assign< distribute_subscript, distribute_subscript >
>
{};
A quick answer for now. I'll try to look more closely at your transform tomorrow.
The meaning of impl::expr
and impl::expr_param
can be understood by looking at documentation for transform_impl
. In short, use impl::expr_param
in the signature of your transform's operator()
, since it adds a const &
to ensure that expressions don't get copied when passed to your function. Use Expr
in your type computations, like in boost::result_of
. impl::expr
isn't useful for much and can be ignored.
As for deep_copy
always forcing your terminals to be held by value, that's pretty much what deep_copy
is for. But maybe you mean something else, since I don't see deep_copy
in your code anywhere.
A word about make_expr
: it forces you to think very carefully about which nodes should be stored by reference and which by value. In the type computation (result_of::make_expr
), reference types are things that should be held by reference, but in the actual function call (proto::make_expr
), you must wrap things with boost::ref
if you want then held by reference. This is weird. Check the user docs for make_expr
.