[proto] const-ness of references
From reading the documentation, I understand that expressions and objects that are wrapped in proto::terminal<> are held by reference. In the following code, in the "assign" grammar, I match proto::terminal<Var<proto::_> > and call _assign on proto::_value. As expected, a reference to the object held in the terminal gets passed to the callable transform, and I can assign some value to it.
template<typename VT> struct Var { VT value; void assign(const VT v) { value = v; } }; struct _assign : proto::callable { typedef void result_type; template<typename VT> result_type operator()(Var<VT> &var) const { var.assign(20); } }; struct assign : proto::or_< proto::when<proto::terminal<Var<proto::_> >, _assign(proto::_value)>
{};
typedef proto::terminal<Var<int> >::type int32_; int main() { int32_ a,b,c; assign()(a); std::cout << proto::value(a).value << std::endl; } Now, I add the following rules to assign grammar : struct assign : proto::or_< proto::when<proto::terminal<Var<proto::_> >, _assign(proto::_value)>, proto::when<proto::assign<proto::terminal<Var<proto::_> >, proto::literal<proto::_> >, _assign(proto::_value(proto::_left), proto::_value(proto::_right))>, proto::when<proto::function<proto::terminal<Var<proto::_> >, proto::literal<proto::_> >, _assign(proto::_value(proto::_left), proto::_value(proto::_right))>
{};
and the following operator() overload to _assign : template<typename VT> result_type operator()(Var<VT> &var, const VT val) const { var.assign(val); } I can merrily call assign()(a=10) and I get the expected behaviour. But when I call assign()(a(20)), I get a compilation error saying that there is no match for operator()(const Var<int> &, const int &). Why is that when I call proto::_value(proto::_left) on a proto::function expression, a *const* reference gets passed to the transform? Did I miss some part of the documentation where this is explained? Manjunath
On 3/4/2010 6:51 AM, Manjunath Kudlur wrote: <snip>
I can merrily call assign()(a=10) and I get the expected behaviour. But when I call assign()(a(20)), I get a compilation error saying that there is no match for operator()(const Var<int> &, const int&). Why is that when I call proto::_value(proto::_left) on a proto::function expression, a *const* reference gets passed to the transform?
This is a bug. Thanks for reporting it. It's now fixed on trunk. -- Eric Niebler BoostPro Computing http://www.boostpro.com
On Wed, Mar 3, 2010 at 4:12 PM, Eric Niebler <eric@boostpro.com> wrote:
On 3/4/2010 6:51 AM, Manjunath Kudlur wrote: <snip>
I can merrily call assign()(a=10) and I get the expected behaviour. But when I call assign()(a(20)), I get a compilation error saying that there is no match for operator()(const Var<int> &, const int&). Why is that when I call proto::_value(proto::_left) on a proto::function expression, a *const* reference gets passed to the transform?
This is a bug. Thanks for reporting it. It's now fixed on trunk.
Thanks for fixing it. I like the fresh off the oven smell of Proto ;) Manjunath
-- Eric Niebler BoostPro Computing http://www.boostpro.com _______________________________________________ Boost-users mailing list Boost-users@lists.boost.org http://lists.boost.org/mailman/listinfo.cgi/boost-users
On Wed, Mar 3, 2010 at 4:20 PM, Manjunath Kudlur <keveman@gmail.com> wrote:
On Wed, Mar 3, 2010 at 4:12 PM, Eric Niebler <eric@boostpro.com> wrote:
On 3/4/2010 6:51 AM, Manjunath Kudlur wrote: <snip>
I can merrily call assign()(a=10) and I get the expected behaviour. But when I call assign()(a(20)), I get a compilation error saying that there is no match for operator()(const Var<int> &, const int&). Why is that when I call proto::_value(proto::_left) on a proto::function expression, a *const* reference gets passed to the transform?
This is a bug. Thanks for reporting it. It's now fixed on trunk.
Similar problem seems to exists for expression of the form Program_(a)(10). Here, Program_ is a function that returns proto::function<proto::terminal<program_>, proto::terminal<Var<proto::_> > >. I wrap it in program_expr, and I define operator()(int) in that expression wrapper class. I try to set the value of the argument passed in, but I run into the same const-ness mismatch compiler error. Please let me know if I am missing something. Here is the complete program: (Look for the line with // COMPILER ERROR comment). #include <boost/proto/proto.hpp> #include <boost/proto/proto_typeof.hpp> #include <iostream> #include <sstream> #include <string> namespace proto=boost::proto; namespace mpl=boost::mpl; template<typename VT> struct Var { VT value; void assign(const VT v) { value = v; } }; struct program_ {}; struct _assign : proto::callable { typedef void result_type; template<typename VT> result_type operator()(Var<VT> &var) const { var.assign(20); } template<typename VT> result_type operator()(Var<VT> &var, const VT val) const { var.assign(val); } }; struct assign : proto::or_< proto::when<proto::terminal<Var<proto::_> >, _assign(proto::_value)>, proto::when<proto::assign<proto::terminal<Var<proto::_> >, proto::literal<proto::_> >, _assign(proto::_value(proto::_left), proto::_value(proto::_right))>, proto::when<proto::function<proto::terminal<Var<proto::_> >, proto::literal<proto::_> >, _assign(proto::_value(proto::_left), proto::_value(proto::_right))>
{};
template<class E> struct program_expr; struct program_generator : proto::or_< proto::when<proto::function<proto::terminal<program_>, proto::terminal<Var<proto::_> > >, proto::pod_generator<program_expr>(proto::_expr)>
{};
struct program_domain : proto::domain<program_generator> {}; template<typename A0> typename proto::result_of::make_expr<proto::tag::function , program_domain , program_ , A0 const &>::type const Program_(A0 const &a0) { return proto::make_expr<proto::tag::function, program_domain>( program_(), boost::ref(a0) ); } template<typename Expr> struct program_expr { BOOST_PROTO_BASIC_EXTENDS(Expr, program_expr<Expr>, program_domain); BOOST_PROTO_EXTENDS_SUBSCRIPT(); typedef void result_type; result_type operator()(int a) const { std::cout << "program with one arg\n"; // COMPILER ERROR on this line (proto::value(proto::child_c<1>(*this))).assign(a); } }; typedef proto::terminal<Var<int> >::type int32_; int main() { int32_ a,b,c; assign()(a=20); assign()(a(30)); Program_(a)(10); std::cout << proto::value(a).value << std::endl; }
On 3/4/2010 11:43 AM, Manjunath Kudlur wrote:
Similar problem seems to exists for expression of the form Program_(a)(10). Here, Program_ is a function that returns proto::function<proto::terminal<program_>, proto::terminal<Var<proto::_> > >.
Not quite. See below. <snip>
template<typename VT> struct Var { VT value; void assign(const VT v) { value = v; } };
Var is a value-type. It holds a datum by-value.
template<typename A0> typename proto::result_of::make_expr<proto::tag::function , program_domain , program_ , A0 const&>::type const Program_(A0 const&a0) { return proto::make_expr<proto::tag::function, program_domain>( program_(), boost::ref(a0) ); }
Program_ takes its argument by const ref. It's argument is therefore immutable. <snip>
typedef proto::terminal<Var<int> >::type int32_;
int main() { int32_ a,b,c;
assign()(a=20); assign()(a(30));
Program_(a)(10);
Here, we're passing "a" to Program_, which takes its argument as const ref. Later, you extract "a" from the Proto tree and try to mutate it. But it's const; obviously that's not going to work. So the first thing you need to do is change Program_ to take its argument by non-const ref. However, when you do that, you'll find that it still doesn't work. And that has to do with how proto::make_expr works. proto::make_expr tries to enforce uniformity in the trees it returns. In particular, it wants all the child nodes to be in the same domain as the parent node it returns. When the domains don't match (as they don't in this case because int32_ is in the default_domain), it passes the child through the domain's generator. The result gets stored by value instead of by reference as you're requesting. The fix is quite simple. Define int32_ as follows: typedef proto::literal<Var<int>, program_domain> int32_; That puts it in the program_domain. Also, you should note that your generator is broken. It only handles the function<terminal,terminal> case. Passing any other expression to it violates its preconditions. You need an otherwise<_> in there. (BTW, I just added a default constructor to literal to make your code compile.) -- Eric Niebler BoostPro Computing http://www.boostpro.com
proto::make_expr tries to enforce uniformity in the trees it returns. In particular, it wants all the child nodes to be in the same domain as the parent node it returns. When the domains don't match (as they don't in this case because int32_ is in the default_domain), it passes the child through the domain's generator.
I follow you up to here.
The result gets stored by value instead of by reference as you're requesting.
I lost you here. In my case, I pass program_() and boost::ref(a0) as children to make_expr. My generator would match otherwise(_) rule (I don't have it in the program I sent, but suppose I add it to program_generator). So the result is the expression unmodified, no? What gets stored by value, where, like you say?
The fix is quite simple. Define int32_ as follows:
typedef proto::literal<Var<int>, program_domain> int32_;
That puts it in the program_domain.
I gave this a try, and it works fine.
Also, you should note that your generator is broken. It only handles the function<terminal,terminal> case. Passing any other expression to it violates its preconditions. You need an otherwise<_> in there.
(BTW, I just added a default constructor to literal to make your code compile.)
Thanks. Manjunath
On 3/4/2010 4:21 PM, Manjunath Kudlur wrote:
proto::make_expr tries to enforce uniformity in the trees it returns. In particular, it wants all the child nodes to be in the same domain as the parent node it returns. When the domains don't match (as they don't in this case because int32_ is in the default_domain), it passes the child through the domain's generator.
I follow you up to here.
The result gets stored by value instead of by reference as you're requesting.
I lost you here. In my case, I pass program_() and boost::ref(a0) as children to make_expr. My generator would match otherwise(_) rule (I don't have it in the program I sent, but suppose I add it to program_generator). So the result is the expression unmodified, no? What gets stored by value, where, like you say?
Well, your domain's generator is a little unusual in that not all the expressions it generates are, in fact, within your domain. That's not a stated post-condition of the Generator parameter, but it probably should be. In short, make_expr is assuming that the generator is returning a new object, and so it is storing it by value. I could hack Proto to make this case work, but doing so makes me uncomfortable. I don't think I want to support generators that don't return expressions in the right domain. -- Eric Niebler BoostPro Computing http://www.boostpro.com
Well, your domain's generator is a little unusual in that not all the expressions it generates are, in fact, within your domain. That's not a stated post-condition of the Generator parameter, but it probably should be. In short, make_expr is assuming that the generator is returning a new object, and so it is storing it by value.
In my case, I am treating the domain mechanism as a means to add a new property to my expression. I want that property to hold only for the expression of the form Program_(...), and that's why I have the generator the way it is. To be honest, I assumed that you provide the domain mechanism just for this purpose (i.e., add new properties to expressions).
I could hack Proto to make this case work, but doing so makes me uncomfortable. I don't think I want to support generators that don't return expressions in the right domain.
Well, I did get my case working by creating a new generator, by_ref_generator, and taking the terminals passed to the Program_ function as references (NOT const refs). Here is the code : #include <boost/proto/proto.hpp> #include <boost/proto/proto_typeof.hpp> #include <iostream> #include <sstream> #include <string> namespace proto=boost::proto; namespace mpl=boost::mpl; template<typename VT> struct Var { VT value; void assign(const VT v) { value = v; } }; struct program_ {}; struct _assign : proto::callable { typedef void result_type; template<typename VT> result_type operator()(Var<VT> &var) const { var.assign(20); } template<typename VT> result_type operator()(Var<VT> &var, const VT val) const { var.assign(val); } }; struct by_ref_generator { BOOST_PROTO_CALLABLE() template<typename Sig> struct result; template<typename This, typename Expr> struct result<This(Expr &)> { typedef Expr& type; }; template<typename This, typename Expr> struct result<This(Expr)> { typedef Expr& type; }; template<typename Expr> typename result<by_ref_generator(Expr &)>::type operator ()(Expr &e) const { return e; } }; struct assign : proto::or_< proto::when<proto::terminal<Var<proto::_> >, _assign(proto::_value)>, proto::when<proto::assign<proto::terminal<Var<proto::_> >, proto::literal<proto::_> >, _assign(proto::_value(proto::_left), proto::_value(proto::_right))>, proto::when<proto::function<proto::terminal<Var<proto::_> >, proto::literal<proto::_> >, _assign(proto::_value(proto::_left), proto::_value(proto::_right))>
{};
template<class E> struct program_expr; struct program_generator : proto::or_< proto::when<proto::function<proto::terminal<program_>, proto::terminal<Var<proto::_> > >, proto::pod_generator<program_expr>(proto::_expr)>, proto::when<proto::terminal<Var<proto::_> >, by_ref_generator(proto::_expr)>, proto::otherwise<proto::_expr>
{};
struct program_domain : proto::domain<program_generator> {}; template<typename A0> typename proto::result_of::make_expr<proto::tag::function , program_domain , program_ , A0 &>::type Program_(A0 &a0) { return proto::make_expr<proto::tag::function, program_domain>( program_(), boost::ref(a0) ); } template<typename Expr> struct program_expr { BOOST_PROTO_BASIC_EXTENDS(Expr, program_expr<Expr>, program_domain); BOOST_PROTO_EXTENDS_SUBSCRIPT(); typedef void result_type; result_type operator()(int a) { std::cout << "program with one arg\n"; (proto::value(proto::child_c<1>(*this))).assign(a); } }; typedef proto::terminal<Var<int> >::type int32_; int main() { int32_ a,b,c; assign()(a=20); assign()(a(35)); Program_(a)(40); std::cout << proto::value(a).value << std::endl; Program_(a)(50); std::cout << proto::value(a).value << std::endl; }
On 3/5/2010 4:38 PM, Manjunath Kudlur wrote:
Well, your domain's generator is a little unusual in that not all the expressions it generates are, in fact, within your domain. That's not a stated post-condition of the Generator parameter, but it probably should be. In short, make_expr is assuming that the generator is returning a new object, and so it is storing it by value.
In my case, I am treating the domain mechanism as a means to add a new property to my expression. I want that property to hold only for the expression of the form Program_(...), and that's why I have the generator the way it is. To be honest, I assumed that you provide the domain mechanism just for this purpose (i.e., add new properties to expressions).
Yes, that's one of the purposes of Proto domains. I was making an important point that you may have missed. This is how things are intended to work: 1) All proto expressions have an associated domain. If you haven't specified one, it is proto::default_domain. 2) All proto domains have an associated generator that, given an expression in the default domain, puts it in the specified domain by adding a domain-specific wrapper. (This is where domain-specific properties and behaviors go.) 3) An expression can only be in one domain (in the current implementation). A domain can have only one generator. There may be many domain-specific wrappers in each domain, each that presents a different interface. The generator's job is to add the right wrapper to a given expression. Most of the time, there is only one wrapper but that need not be the case. 4) All operations on proto expressions produce new expressions in the same domain as its constituents. When I said that your generator was unusual, it was because it doesn't follow (2) above ... the expression emitted by your generator is sometimes in the program_domain, and sometimes in the default domain. Look again at the code I posted back on March 1: http://article.gmane.org/gmane.comp.lib.boost.user/56408 There are two wrappers with two different behaviors, both in the same domain. The generator always produced expressions in the same domain, but sometimes with different behaviors. This is what you want. <snipping the rest because I don't have time to look in detail right now.> -- Eric Niebler BoostPro Computing http://www.boostpro.com
Yes, that's one of the purposes of Proto domains. I was making an important point that you may have missed. This is how things are intended to work:
1) All proto expressions have an associated domain. If you haven't specified one, it is proto::default_domain.
2) All proto domains have an associated generator that, given an expression in the default domain, puts it in the specified domain by adding a domain-specific wrapper. (This is where domain-specific properties and behaviors go.)
3) An expression can only be in one domain (in the current implementation). A domain can have only one generator. There may be many domain-specific wrappers in each domain, each that presents a different interface. The generator's job is to add the right wrapper to a given expression. Most of the time, there is only one wrapper but that need not be the case.
4) All operations on proto expressions produce new expressions in the same domain as its constituents.
When I said that your generator was unusual, it was because it doesn't follow (2) above ... the expression emitted by your generator is sometimes in the program_domain, and sometimes in the default domain.
Thanks for the detailed explanation. Things are much clear now. Manjunath
participants (2)
-
Eric Niebler
-
Manjunath Kudlur