Boost logo

Boost :

Subject: [boost] [proto] transforms && rvalue refs
From: troy d. straszheim (troy_at_[hidden])
Date: 2009-03-29 16:47:58


I'm playing with proto on gcc 4.3 with c++0x enabled and trying to see
if I can use rvalue refs inside callable transforms, to tell what
is/isn't temporary while transforming an expression. I have a
different way to do this, involving embedding the 'rvalueness' of an
object in its type... yuck. Hopefully something better is out there.

The use case:

   a = exp(exp(b));

(as below), where a and b are terminals and exp is a proto lazy
function. This expression should result in only one copy of b... You'd
want the inner call 'exp(b)' to see that it is being passed an lvalue,
copy the value to a temp, modify and return std::move(temp). The outer
call to exp() should see that an rvalue has arrived and feel free to
overwrite it and return it by std::move. I think.

The closest I can get is below. I thought I'd ask for a sanity-check
on this before go any further... I have the feeling the check will come
back negative. Thanks in advance,

-t

#include <boost/proto/proto.hpp>
#include <boost/ref.hpp>
#include <cassert>

namespace bp = boost::proto;

//
// Tag for calls to exp() in our DSEL
//
struct exp_tag
{
   friend std::ostream& operator<<(std::ostream& os, exp_tag)
   {
     return os << "exp_tag";
   }
};

//
// The terminal. Assume this thing is very heavy. Some construtors
// have assert(0) in them to verify they aren't called.
//
//
struct array_impl
{
   std::string name;

   array_impl()
     : name("NONAME")
   { }

   array_impl(const std::string& name_)
     : name(name_)
   {
     std::cout << "name=" << name << "\n";
   }

   //
   // constructors
   //

   array_impl(array_impl& rhs)
     : name(rhs.name + "_copied")
   { }

   array_impl(const array_impl& rhs)
     : name(rhs.name+ "_copied")
   { }

   array_impl(array_impl&& rhs)
   {
     name.swap(rhs.name);
     name += "_moved";
   }

   array_impl& operator=(array_impl&& rhs)
   {
     name.swap(rhs.name);
     name += "_moved";
     return *this;
   }

   //
   // not called
   //

   array_impl& operator=(array_impl& rhs)
   {
     assert(0); // just to be sure what is and isn't getting called
   }

   array_impl& operator=(const array_impl& rhs)
   {
     assert(0); // just to be sure what is and isn't getting called
   }

   array_impl& operator=(const array_impl&& rhs)
   {
     assert(0); // just to be sure what is and isn't getting called
   }

   friend std::ostream& operator<<(std::ostream& os,
                                  const array_impl&)
   {
     return os << "array_impl";
   }
};

//
// The transform for calls to exp. You'd like to be able to see if
// this is getting called with a movable object.
//
struct UnaryFnCall : bp::callable
{
   typedef array_impl result_type;

   template <typename Tag>
   result_type
   operator()(Tag, const array_impl& t)
   {
     std::cout << "transform a const ref\n";
     array_impl tmp(t);
     return std::move(tmp);
   }

   template <typename Tag>
   result_type
   operator()(Tag, array_impl&& t)
   {
     // this guy doesn't get called, but I'd like him to.
     assert(0); // if this trips, something good happened.
     return std::move(t);
   }
};

struct Grammar :
   bp::or_<bp::when<bp::terminal<array_impl>, bp::_value>,
          bp::when<bp::unary_expr<exp_tag, Grammar>,
                   UnaryFnCall(exp_tag(),
                               Grammar(bp::_child0))>
>
{ };

template <typename T>
struct Expression;

struct Domain
   : bp::domain<bp::pod_generator<Expression>, Grammar>
{ };

template <typename Expr>
struct Expression
{
   BOOST_PROTO_EXTENDS(Expr, Expression<Expr>, Domain);
};

template<class T>
typename bp::result_of::make_expr<
   exp_tag,
   Domain,
   T&
>::type
exp (T&& t)
{
   return bp::make_expr<exp_tag, Domain>(boost::ref(t));
}

//
// Our expression type that
//
struct array : public Expression<bp::terminal<array_impl>::type>
{
   typedef array_impl impl_t;

   impl_t& self() { return boost::proto::value(*this); }

   array() { assert(0); }

   array(const std::string& name)
   {
     self().name = name;
   }

   array(const array&) { assert(0); }

   array(array&&) { assert(0); }

   template <typename Expr>
   array& operator=(Expr const& expr)
   {
     bp::display_expr(expr, std::cout);
     BOOST_MPL_ASSERT(( bp::matches<Expr, Grammar> ));

     typedef typename boost::result_of<Grammar(Expr const&)>::type result_t;

     result_t r = Grammar()(expr);

     self().operator=(std::move(r));

     return *this;
   }
};

int main(int, char**)
{
   array a("a"), b("b");

   // Here, only one copy is made
   a = exp(b);
   std::cout << "a's name:" << a.self().name << "\n\n";

   // Here, two copies are made
   a = exp(exp(exp(b)));
   std::cout << "a's name:" << a.self().name << "\n";

}


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk