
Hello everyone, I've been meaning to learn to use Boost.Proto for quite some time and I've finally started getting my hands dirty in code ever since that fantastic video from BoostCon 2010. After the usual exercises that are presented in it and the docs, I've set up my own goal of trying to write composable futures (something that I think quite a lot of people have been doing in their own time). You can see the code at http://pastebin.com/DW4LckMP and I'd welcome comments on what I've been doing wrong and how I could improve the design. I also welcome answers to my following questions: * regarding copying, the docs present the following example: auto sum = boost::proto::lit(1) + 2; here, since the 1 is held by a reference which will outlive (in the variable sum) the actual literal expression (in the call to proto::lit) we will have UB. To avoid this exact problem, and since Boost.Proto is not move aware and std::future is move-only, my terminals are actually of the form: future_expresion<typename boost::proto::terminal<std::future&>::type> (where future_expression is the expression wrapper). Can I do this? Obviously I still have a problem if I do: std::future<int> fint = ... auto sum = ns::compose(fint) + 3; // then use sum.get() don't I? I can't solve it by using proto::deep_copy since std::future is move-only (apparently my terminals get stripped of their reference). This means that the 'correct' way of doing the above would be std::future<int> fint = ... auto const operand = 3; auto sum = ns::compose(fint) + operand; // use sum.get() thus separating lifetimes from computations (expression trees, really). * the following doesn't compile: struct local { int method() { return 1; } int method() const { return 2; } }; auto method = (int (local::*)()) &local::method; auto cmethod = (int (local::*)() const) &local::method; auto f1 = std::async([] { return local(); }); auto f2 = std::async([] { return local(); }); auto r1 = (ns::compose(f1)->*cmethod)(); // this line OK auto r2 = (ns::compose(f2)->*method)(); // Not OK! assert( r1.get() == 2 && r2.get() == 1 ); ( you can replace the above in main() instead of the previous ) Apparently the problem is an attempt to convert a pointer to const local to a pointer to non-const local. Invoking cmethod instead (with the appropriate assertion) is fine. I've checked that the result of ns::compose(f1).get() is indeed a local and not a const local via static_assert. This is using Boost 1.42. Any idea?

On 11/8/2010 3:36 PM, Luc Danton wrote:
Hello everyone,
I've been meaning to learn to use Boost.Proto for quite some time and I've finally started getting my hands dirty in code ever since that fantastic video from BoostCon 2010. After the usual exercises that are presented in it and the docs, I've set up my own goal of trying to write composable futures (something that I think quite a lot of people have been doing in their own time).
That's an ambitious project for your first outing! Proto has a futures example that you may have seen (libs/proto/example/futures.cpp), but it doesn't address your concerns below.
You can see the code at http://pastebin.com/DW4LckMP and I'd welcome comments on what I've been doing wrong and how I could improve the design. I also welcome answers to my following questions:
I've come to really dislike Proto's contexts. I personally prefer grammars+transforms.
* regarding copying, the docs present the following example: auto sum = boost::proto::lit(1) + 2; here, since the 1 is held by a reference which will outlive (in the variable sum) the actual literal expression (in the call to proto::lit) we will have UB.
Correct. Expression templates and "auto/decltype" don't co-exist happily. Much care needs to be taken if storing these things in local variables is part of your usage requirements.
To avoid this exact problem, and since Boost.Proto is not move aware and std::future is move-only, my terminals are actually of the form:
future_expresion<typename boost::proto::terminal<std::future&>::type>
(where future_expression is the expression wrapper). Can I do this? Obviously I still have a problem if I do:
std::future<int> fint = ... auto sum = ns::compose(fint) + 3; // then use sum.get()
don't I?
Yes.
I can't solve it by using proto::deep_copy since std::future is move-only (apparently my terminals get stripped of their reference). This means that the 'correct' way of doing the above would be
std::future<int> fint = ... auto const operand = 3; auto sum = ns::compose(fint) + operand; // use sum.get()
thus separating lifetimes from computations (expression trees, really).
And that doesn't solve the problem completely, because intermediate expression nodes are also held by reference. Expressions any larger than this will be UB. <snip>
Any idea?
As you have learned, Proto is not move-aware. My hand-wavy solution would be to (a) be sure that everything is held by value in an ET. With 1.42, that's by using proto::by_value_generator. In 1.44, the preferred solution is with a domain-specific as_child implementation. And (b) to wrap move-only types in a "copyable" type that, in its copy ctor, moves the wrapped value. I'll see if I can come up with an example that demonstrates. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On 11/9/2010 11:27 AM, Eric Niebler wrote:
Any idea?
As you have learned, Proto is not move-aware. My hand-wavy solution would be to (a) be sure that everything is held by value in an ET. With 1.42, that's by using proto::by_value_generator. In 1.44, the preferred solution is with a domain-specific as_child implementation. And (b) to wrap move-only types in a "copyable" type that, in its copy ctor, moves the wrapped value.
I'll see if I can come up with an example that demonstrates.
Here is a Proto algorithm that deep-copies a tree that contains a move-only object. This doesn't directly answer your question, but might get you moving in the right direction. (Tested on VC10.) #include <boost/proto/proto.hpp> #include <typeinfo> #include <string> #include <iostream> namespace proto=boost::proto; namespace mpl=boost::mpl; using proto::_; // A move-only type struct move_only { move_only() : what_("valid") {} move_only(move_only && that) : what_("valid") { that.what_ = "moved from"; } ~move_only() { what_ = "deleted"; } char const *what_; private: move_only(move_only const &); move_only& operator=(move_only const &); }; // A wrapper for move-only types that moves on copy template<typename T> struct move_on_copy { explicit move_on_copy(T && t) : t_(std::move(t)) {} move_on_copy(move_on_copy const & that) : t_(std::move(that.t_)) {} private: move_on_copy &operator=(move_on_copy const &); mutable T t_; }; // A callable that wraps a move-only type in move_on_copy struct _move : proto::callable { template<typename Sig> struct result; template<typename This, typename T> struct result<This(T)> { typedef move_on_copy<typename boost::remove_reference<T>::type> type; }; template<typename T> move_on_copy<typename boost::remove_reference<T>::type> operator()(T && t) const { return move_on_copy<typename boost::remove_reference<T>::type>(std::move(t)); } }; // A type-trait we can use to detect move-only types template<typename T> struct is_move_only : mpl::false_ {}; template<typename T> struct is_move_only<T &> : is_move_only<T> {}; template<typename T> struct is_move_only<T const> : is_move_only<T> {}; template<> struct is_move_only<move_only> : mpl::true_ {}; // A pattern that matches move-only terminals struct MoveOnlyTerminal : proto::and_< proto::terminal<_> , proto::if_<is_move_only<proto::_value>()> > {}; // An algorithm that deep-copies a Proto expression tree, wrapping // move-only types in move_on_copy struct DeepCopy : proto::or_< proto::when<MoveOnlyTerminal, proto::_make_terminal(_move(proto::_value))> , proto::when<proto::terminal<proto::_>, proto::_make_terminal(proto::_byval(proto::_value))> , proto::nary_expr<proto::_, proto::vararg<DeepCopy> > > {}; int main() { move_only a; move_only b(std::move(a)); move_on_copy<move_only> e(std::move(b)); move_on_copy<move_only> f(e); // move! proto::terminal<move_only>::type y = {std::move(b)}; // This deep-copies the tree, wrapping all move-only types // in move_on_copy. DeepCopy()(1 + y + 42); } HTH, -- Eric Niebler BoostPro Computing http://www.boostpro.com
participants (2)
-
Eric Niebler
-
Luc Danton