Search code examples
c++boost-proto

Boost.Proto and Complex Transform


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 terminals 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 >
    >
{};

Solution

  • 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.