[GSoC][Phoenix3] Regarding mutability of arguments passed to phoenix expressions

Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this. Example: int i( 1 ); ( ++phoenix::arg_names::arg1 )( i ); The result of ++phoenix::arg_names::arg1 will be 2, i will have the value 2 after the expression is evaluated. The other approach would follow a more functional programming style as the data will not be mutable int i( 1 ); [1] ( ++phoenix::arg_names::arg1 )( i ); [2] ( ++phoenix::arg_names::arg1 + phoenix::arg_names::arg1 )( i ); The result of [1] will be 2, however i does not get changed (it remains to be 1). The result of [2] will be 3. i still unchanged. This example would be working "out-of-the-box" with the current prototypes. To get the mutability back into phoenix i propose the use of phoenix::ref. I favor the second approach. However, this would break API compatibility between the existing implementation. The question i want to ask the current and prospective users if it feasible to break your existing code? Is there a lot of existing code depending on this behaviour? Does it make sense to break code in such a way? Regards, Thomas

----- Original Message ----- From: "Thomas Heller" <thom.heller@googlemail.com> To: <boost@lists.boost.org> Sent: Tuesday, April 27, 2010 9:49 PM Subject: [boost] [GSoC][Phoenix3] Regarding mutability of arguments passedto phoenix expressions
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this. Example:
int i( 1 ); ( ++phoenix::arg_names::arg1 )( i );
The result of ++phoenix::arg_names::arg1 will be 2, i will have the value 2 after the expression is evaluated.
The other approach would follow a more functional programming style as the data will not be mutable
int i( 1 ); [1] ( ++phoenix::arg_names::arg1 )( i ); [2] ( ++phoenix::arg_names::arg1 + phoenix::arg_names::arg1 )( i );
The result of [1] will be 2, however i does not get changed (it remains to be 1). The result of [2] will be 3. i still unchanged. This example would be working "out-of-the-box" with the current prototypes. To get the mutability back into phoenix i propose the use of phoenix::ref.
I favor the second approach. However, this would break API compatibility between the existing implementation. The question i want to ask the current and prospective users if it feasible to break your existing code? Is there a lot of existing code depending on this behaviour? Does it make sense to break code in such a way?
Hi, Even if Phoenix has not been released yet on Boost, I don't think that breaking code will be desirable. I don't know very well Phoenix, but I think that if you use inmutable functions you will get what you want If I dont want to change i I will write instead of [1,2] int i( 1 ); [3] ( phoenix::arg_names::arg1 + 1)( i ); result==2 i==1 [4] ( phoenix::arg_names::arg1 + 2)( i ); So if you don't want to change your parameter don't use functions that change it. I'm not saying that your approach is not the correct one, but in order to avoid break code what do you think to define inmmutable arguments as a different class and use ref to get mutability? Best, _____________________ Vicente Juan Botet Escribá http://viboes.blogspot.com/

Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
[snip]
The other approach would follow a more functional programming style as the data will not be mutable [snip] To get the mutability back into phoenix i propose the use of phoenix::ref.
I favor the second approach. However, this would break API compatibility between the existing implementation.
I think default immutability is safer and less surprising; C++ uses copy-by-value by default. Even if this breaks code, we are talking about the third generation of an as yet unofficial library. Now's the time to do it. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this. Example:
int i( 1 ); ( ++phoenix::arg_names::arg1 )( i );
The result of ++phoenix::arg_names::arg1 will be 2, i will have the value 2 after the expression is evaluated.
That's the behaviour I want.
[2] ( ++phoenix::arg_names::arg1 + phoenix::arg_names::arg1 )( i );
The result of [2] will be 3. i still unchanged.
Even if `i' was passed by value, I would have expected 4, assuming left to right evaluation (which I think is the case here because it's an user-defined operator+). Otherwise you shouldn't be able to do ++phoenix::arg_names::arg1 in the first place...

On 4/28/2010 10:19 AM, Mathias Gaunard wrote:
Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the long haul, I think this will satisfy the Principle of Least Surprise. And yes, now is the time for breaking changes. It is likely to be unavoidable, anyway. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
On 4/28/2010 10:19 AM, Mathias Gaunard wrote:
Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the
The introducer uses & before names to get references. By value is the default.
long haul, I think this will satisfy the Principle of Least Surprise.
Agreed. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

On Wednesday 28 April 2010 19:43:43 Stewart, Robert wrote:
Eric Niebler wrote:
On 4/28/2010 10:19 AM, Mathias Gaunard wrote:
Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the
The introducer uses & before names to get references. By value is the default.
long haul, I think this will satisfy the Principle of Least Surprise.
giving that pure functional phoenix stuff a little more thought. Consider the following code (Reminder, this is just an example): template< typename Container > Container & push_back_impure( Container & c, typename Container::value_type const & v ) { c.push_back( v ); return c; } template< typename Container > Container push_back_pure( Container c, typename Container::value_type const & v ) { c.push_back( v ); return c; } int main() { std::vector<int> v; // this could be equivalent to a phoenix expression like // ( phoenix::push_back( phoenix::_1, 4 ) )( v ); push_back_impure( v, 4 ); // whereas this needs to be written as (to get the same effect): // v = ( phoenix::push_back( phoenix::_1, 5 ) )( v ); // looks like a performance hog v = push_back_pure( v, 5 ); } I think both versions have a some valid use cases. However I would like to postpone that discussion until I have working version of the new phoenix. Joel, could you elaborate why you implemented the current phoenix version as it is?

Thomas Heller wrote:
On Wednesday 28 April 2010 19:43:43 Stewart, Robert wrote:
Eric Niebler wrote:
On 4/28/2010 10:19 AM, Mathias Gaunard wrote:
Thomas Heller wrote:
The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the
The introducer uses & before names to get references. By value is the default.
long haul, I think this will satisfy the Principle of Least Surprise.
giving that pure functional phoenix stuff a little more thought. Consider the following code (Reminder, this is just an example):
template< typename Container > Container & push_back_impure( Container & c, typename Container::value_type const & v ) { c.push_back( v ); return c; }
template< typename Container > Container push_back_pure( Container c, typename Container::value_type const & v ) { c.push_back( v ); return c; }
Why not pass the container by reference to const? You could even use Boost.CallTraits to manage by value versus reference to const.
int main() { std::vector<int> v;
// this could be equivalent to a phoenix expression like // ( phoenix::push_back( phoenix::_1, 4 ) )( v ); push_back_impure( v, 4 );
// whereas this needs to be written as (to get the same effect): // v = ( phoenix::push_back( phoenix::_1, 5 ) )( v ); // looks like a performance hog v = push_back_pure( v, 5 );
The suggestion was that the latter could be handled by explicitly making _1 pass by reference to non-const using ref() or something. Thus, if writing code like you've shown is a performance problem, write the impure version using ref(). _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

On 4/28/2010 1:26 PM, Eric Niebler wrote:
On 4/28/2010 10:19 AM, Mathias Gaunard wrote:
Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the long haul, I think this will satisfy the Principle of Least Surprise.
Following up on this ... I was conflating lambda arguments with captured variables that appear in the lambda body. I just looked over the draft standard, and variables are captured by reference by default and need special syntax to capture them by value. 5.1.2/14-15:
14 An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an &. For each entity captured by copy, an unnamed nonstatic data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [ Note: if the captured entity is a reference to a function, the corresponding data member is also a reference to a function. —end note ]
15 An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
This seems pretty clear to me, so I think Phoenix has it backwards wrt variable capture. I find it interesting that functions are called out as a special case, but not arrays or abstract types. Presumably it's simply an error to try to capture these by value, but the standard could simply DWIM. As for the lambda arguments, Phoenix can take no guidance from the standard since the built-in lambdas require you to define an argument list, making them monomorphic. We could aim for consistency with the capture behavior and pass by reference, or be consistent with std::bind which uses rvalue refs and perfect forwarding (darn), or TR1 bind and boost::bind which accepts parameters by value. Given the choices, I think pass-by-value makes the most sense. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On 6/11/10 11:12 PM, Eric Niebler wrote:
On 4/28/2010 1:26 PM, Eric Niebler wrote:
On 4/28/2010 10:19 AM, Mathias Gaunard wrote:
Thomas Heller wrote:
Regarding arguments to phoenix expressions there are two possibilities. The first is: Arguments are mutable. The current phoenix is implemented to be able to this.
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the long haul, I think this will satisfy the Principle of Least Surprise.
Following up on this ... I was conflating lambda arguments with captured variables that appear in the lambda body. I just looked over the draft standard, and variables are captured by reference by default and need special syntax to capture them by value. 5.1.2/14-15:
That's interesting. Is there a rationale?
14 An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an&. For each entity captured by copy, an unnamed nonstatic data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [ Note: if the captured entity is a reference to a function, the corresponding data member is also a reference to a function. —end note ]
15 An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
This seems pretty clear to me, so I think Phoenix has it backwards wrt variable capture. I find it interesting that functions are called out as a special case, but not arrays or abstract types. Presumably it's simply an error to try to capture these by value, but the standard could simply DWIM.
As for the lambda arguments, Phoenix can take no guidance from the standard since the built-in lambdas require you to define an argument list, making them monomorphic. We could aim for consistency with the capture behavior and pass by reference, or be consistent with std::bind which uses rvalue refs and perfect forwarding (darn), or TR1 bind and boost::bind which accepts parameters by value.
Given the choices, I think pass-by-value makes the most sense.
So do I, but why do you think does it make the most sense? It would be good to explore this further. I am all for consistency, but now I'm less sure about which way to go. By value is safer. How about by reference? What's the advantage there? rvalue refs and perfect forwarding is ideal, but what about older compilers and compatibility? What are the disadvantages of each approach? I'm still biased towards by-value, but then again, I'd like to hear the pros and cons of each approach. Regards, -- Joel de Guzman http://www.boostpro.com http://spirit.sf.net

At Fri, 11 Jun 2010 11:12:46 -0400, Eric Niebler wrote:
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the long haul, I think this will satisfy the Principle of Least Surprise.
Following up on this ... I was conflating lambda arguments with captured variables that appear in the lambda body. I just looked over the draft standard, and variables are captured by reference by default and need special syntax to capture them by value. 5.1.2/14-15:
I don't see how you draw that conclusion from the text below.
14 An entity is captured by copy if it is implicitly captured and the capture-default is = or if it is explicitly captured with a capture that does not include an &. For each entity captured by copy, an unnamed nonstatic data member is declared in the closure type. The declaration order of these members is unspecified. The type of such a data member is the type of the corresponding captured entity if the entity is not a reference to an object, or the referenced type otherwise. [ Note: if the captured entity is a reference to a function, the corresponding data member is also a reference to a function. —end note ]
15 An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.
My understanding is that the only implicit captures happen when there is a capture-default, i.e.: void f() { int a; []{ return a; } // error - no capture-default // capture-default is & [&]{ return a; } // a implicitly captured by reference // implicit capture and capture-default is = [=]{ return a; } // a implicitly captured by copy // explicit capture with a capture that does not include & [a]{ return a; } // a captured by copy }
As for the lambda arguments, Phoenix can take no guidance from the standard since the built-in lambdas require you to define an argument list, making them monomorphic. We could aim for consistency with the capture behavior and pass by reference, or be consistent with std::bind which uses rvalue refs and perfect forwarding (darn), or TR1 bind and boost::bind which accepts parameters by value.
Given the choices, I think pass-by-value makes the most sense.
why not perfect forwarding? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On 6/11/2010 11:49 AM, David Abrahams wrote:
At Fri, 11 Jun 2010 11:12:46 -0400, Eric Niebler wrote:
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the long haul, I think this will satisfy the Principle of Least Surprise.
Following up on this ... I was conflating lambda arguments with captured variables that appear in the lambda body. I just looked over the draft standard, and variables are captured by reference by default and need special syntax to capture them by value. 5.1.2/14-15:
I don't see how you draw that conclusion from the text below.
I went back, and you're right. 5.1.2/11-12 make it clear that implicit capture happens only when a default capture mode is specified. Good. That gives us an opportunity to add a new feature to Phoenix: default capture modes. I could see something like: ref[ phoenix-lambda-expression ] makes default capture by-ref, and val[ phoenix-lambda-expression ] makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference. ref and val simply change how the resulting tree is interpreted. In addition, we can let users pick and choose on a per-variable basis using the the ref(x) and val(x) on every use of the variable x within the expression.
As for the lambda arguments, Phoenix can take no guidance from the standard since the built-in lambdas require you to define an argument list, making them monomorphic. We could aim for consistency with the capture behavior and pass by reference, or be consistent with std::bind which uses rvalue refs and perfect forwarding (darn), or TR1 bind and boost::bind which accepts parameters by value.
Given the choices, I think pass-by-value makes the most sense.
why not perfect forwarding?
Because it's too expensive to simulate in C++98. -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
That gives us an opportunity to add a new feature to Phoenix: default capture modes. I could see something like:
ref[ phoenix-lambda-expression ]
makes default capture by-ref, and
val[ phoenix-lambda-expression ]
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
What if the variable is not copyable? Copying is an expensive operation as well, why do it when you don't need to?

On 6/11/2010 12:17 PM, Mathias Gaunard wrote:
Eric Niebler wrote:
That gives us an opportunity to add a new feature to Phoenix: default capture modes. I could see something like:
ref[ phoenix-lambda-expression ]
makes default capture by-ref, and
val[ phoenix-lambda-expression ]
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
What if the variable is not copyable?
If that can be detected at compile-time, we can avoid the copy.
Copying is an expensive operation as well, why do it when you don't need to?
There's the ref(x) syntax for avoiding the copy completely. The reason we have to take a copy when building the expression is because if we want by-value capture to be the default then these two should be equivalent: int x = 0; auto f0 = val[ x += _1 ]; // capture x by val auto f1 = x += _1; // capture x by val f0(1); f2(1); // x is unchanged here In the second case, if we simply capture by reference, we are never given a chance to change the capture to by-value before we hand the lambda over for evaluation elsewhere. Instead, we capture both by value and by reference, and the default is to use the stored copy. Despite the contrived-ness of this example, which makes by-value copy seem like a bad default, I still think it's the way to go. BLL captures by value (but interestingly makes an exception for the case above). -- Eric Niebler BoostPro Computing http://www.boostpro.com

At Fri, 11 Jun 2010 12:12:27 -0400, Eric Niebler wrote:
On 6/11/2010 11:49 AM, David Abrahams wrote:
At Fri, 11 Jun 2010 11:12:46 -0400, Eric Niebler wrote:
We now have lambdas in C++0x. IMO, we should be paying attention to the default semantics of lambdas which, IIRC, accept their arguments by value(?), and have special syntax for accepting their arguments by reference. Someone should correct me if I got that backwards. In the long haul, I think this will satisfy the Principle of Least Surprise.
Following up on this ... I was conflating lambda arguments with captured variables that appear in the lambda body. I just looked over the draft standard, and variables are captured by reference by default and need special syntax to capture them by value. 5.1.2/14-15:
I don't see how you draw that conclusion from the text below.
I went back, and you're right. 5.1.2/11-12 make it clear that implicit capture happens only when a default capture mode is specified. Good.
That gives us an opportunity to add a new feature to Phoenix: default capture modes. I could see something like:
ref[ phoenix-lambda-expression ]
makes default capture by-ref, and
val[ phoenix-lambda-expression ]
I would be cautious about emulating C++0x lambdas. I'm not sure yet that they don't suck.
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
!! Sounds expensive.
ref and val simply change how the resulting tree is interpreted. In addition, we can let users pick and choose on a per-variable basis using the the ref(x) and val(x) on every use of the variable x within the expression.
As for the lambda arguments, Phoenix can take no guidance from the standard since the built-in lambdas require you to define an argument list, making them monomorphic. We could aim for consistency with the capture behavior and pass by reference, or be consistent with std::bind which uses rvalue refs and perfect forwarding (darn), or TR1 bind and boost::bind which accepts parameters by value.
Given the choices, I think pass-by-value makes the most sense.
why not perfect forwarding?
Because it's too expensive to simulate in C++98.
Oh, I meant for 0x. Be cautious, also, about designing for C++98 at the expense of a good 0x interface. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On 6/11/2010 12:27 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:12:27 -0400,
That gives us an opportunity to add a new feature to Phoenix: default capture modes. I could see something like:
ref[ phoenix-lambda-expression ]
makes default capture by-ref, and
val[ phoenix-lambda-expression ]
I would be cautious about emulating C++0x lambdas. I'm not sure yet that they don't suck.
They suck, but not for this reason. ;-)
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
!! Sounds expensive.
The plan has always been to make capture-by-value the default. Saving off a reference as well is free.
why not perfect forwarding?
Because it's too expensive to simulate in C++98.
Oh, I meant for 0x. Be cautious, also, about designing for C++98 at the expense of a good 0x interface.
Agreed in principle, but I don't think we have much choice at this point. We want Phoenix to be usable today. How long will it before we can reasonably assume everyone doing C++ has variadics and rvalue refs? A long time, I think. As a compromise, we can put the variadic/rvalue ref implementation on a switch and let users opt in if they have the features and don't care about portability. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On 6/11/2010 12:36 PM, Eric Niebler wrote:
On 6/11/2010 12:27 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:12:27 -0400, Eric Niebler wrote
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
!! Sounds expensive.
The plan has always been to make capture-by-value the default. Saving off a reference as well is free.
Well, there's a hitch. ref[ bigobj += _1 ] Users might reasonably expect this to not make a copy of bigobj, and the way to avoid it is not obvious: ref[ ref(bigobj) += _1 ] Clearly the user has already stated their intention to capture bigobj by reference and shouldn't have to say it again. I think default capture modes may be unworkable if we want the default mode to be by-value. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On Fri, Jun 11, 2010 at 6:03 PM, Eric Niebler <eric@boostpro.com> wrote:
On 6/11/2010 12:36 PM, Eric Niebler wrote:
On 6/11/2010 12:27 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:12:27 -0400, Eric Niebler wrote
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
!! Sounds expensive.
The plan has always been to make capture-by-value the default. Saving off a reference as well is free.
Well, there's a hitch.
ref[ bigobj += _1 ]
Users might reasonably expect this to not make a copy of bigobj, and the way to avoid it is not obvious:
ref[ ref(bigobj) += _1 ]
Clearly the user has already stated their intention to capture bigobj by reference and shouldn't have to say it again.
I think default capture modes may be unworkable if we want the default mode to be by-value.
unless you forbid 'naked' lambdas: int x = 0; auto f0 = lambda [ x += _1 ]; // capture x by val auto f1 = lambda_r [ x += _1 ]; // capture x by ref auto f2 = x += _1 ; // just a proto expression tree, not a lambda f0(1); //ok f2(1); //ok f3(1); // no operator() The lambda syntax becomes more heavy weight, but lambdas do stand up more in code. You would have to protect nested lambdas anyway ... -- gpd

On 6/11/2010 1:24 PM, Giovanni Piero Deretta wrote:
On Fri, Jun 11, 2010 at 6:03 PM, Eric Niebler <eric@boostpro.com> wrote:
Well, there's a hitch.
ref[ bigobj += _1 ]
Users might reasonably expect this to not make a copy of bigobj, and the way to avoid it is not obvious:
ref[ ref(bigobj) += _1 ]
Clearly the user has already stated their intention to capture bigobj by reference and shouldn't have to say it again.
I think default capture modes may be unworkable if we want the default mode to be by-value.
unless you forbid 'naked' lambdas:
int x = 0; auto f0 = lambda [ x += _1 ]; // capture x by val auto f1 = lambda_r [ x += _1 ]; // capture x by ref auto f2 = x += _1 ; // just a proto expression tree, not a lambda
f0(1); //ok f2(1); //ok f3(1); // no operator()
The lambda syntax becomes more heavy weight, but lambdas do stand up more in code. You would have to protect nested lambdas anyway ...
That's true, and I /personally/ wouldn't be opposed to that; I just don't have a strong preference. This should be Joel's call. -- Eric Niebler BoostPro Computing http://www.boostpro.com

At Fri, 11 Jun 2010 12:36:56 -0400, Eric Niebler wrote:
On 6/11/2010 12:27 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:12:27 -0400,
makes it by-val. Under the hood, Phoenix would actually be storing every captured variable both by value and by reference.
!! Sounds expensive.
The plan has always been to make capture-by-value the default. Saving off a reference as well is free.
Yeah, that's a fine default. I mean it's expensive when the user decides he really wanted the reference.
Be cautious, also, about designing for C++98 at the expense of a good 0x interface.
Agreed in principle, but I don't think we have much choice at this point. We want Phoenix to be usable today.
Yes, but consider tolerating some inconvenience or ugliness in 03 if it makes things better under 0x. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

At Fri, 11 Jun 2010 12:36:56 -0400, Eric Niebler wrote:
On 6/11/2010 12:27 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:12:27 -0400,
That gives us an opportunity to add a new feature to Phoenix: default capture modes. I could see something like:
ref[ phoenix-lambda-expression ]
makes default capture by-ref, and
val[ phoenix-lambda-expression ]
I would be cautious about emulating C++0x lambdas. I'm not sure yet that they don't suck.
They suck, but not for this reason. ;-)
I don't know. The more I learn about the capture rules, suckier they seem. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On 6/12/2010 2:04 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:36:56 -0400, Eric Niebler wrote:
On 6/11/2010 12:27 PM, David Abrahams wrote:
I would be cautious about emulating C++0x lambdas. I'm not sure yet that they don't suck.
They suck, but not for this reason. ;-)
I don't know. The more I learn about the capture rules, suckier they seem.
Ha! :-) I've seen your msgs on the std- reflector. Don't worry, Phoenix captures everything consistently, regardless of whether it's a local variable or a member variable. It can't do otherwise. -- Eric Niebler BoostPro Computing http://www.boostpro.com

At Sat, 12 Jun 2010 14:07:04 -0400, Eric Niebler wrote:
On 6/12/2010 2:04 PM, David Abrahams wrote:
At Fri, 11 Jun 2010 12:36:56 -0400, Eric Niebler wrote:
On 6/11/2010 12:27 PM, David Abrahams wrote:
I would be cautious about emulating C++0x lambdas. I'm not sure yet that they don't suck.
They suck, but not for this reason. ;-)
I don't know. The more I learn about the capture rules, suckier they seem.
Ha! :-) I've seen your msgs on the std- reflector. Don't worry, Phoenix captures everything consistently, regardless of whether it's a local variable or a member variable. It can't do otherwise.
I know. I'm beginning to think that if we could only (appropriately) change the rules for overriding -> and add (an appropriate) operator., Phoenix would be the indisputable hands-down winner. As it is, IMO it's still going to be better than the built-in lambdas. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Le 12/06/2010 20:39, David Abrahams a écrit : Quoted out of order:
As it is, IMO it's still going to be better than the built-in lambdas.
The only real problem I have with built-in lambdas myself is the lack of polymorphic ones. The fact they're quite verbose (unlike the very terse lambda or phoenix v2) is something I dislike, but that I can live with. For captures, I don't see how anyone would want to do anything but capture everything by reference.
I know. I'm beginning to think that if we could only (appropriately) change the rules for overriding -> and add (an appropriate) operator., Phoenix would be the indisputable hands-down winner.
Unfortunately, the only decent way of overloading operator. I can think of is to take a polymorphic function object that takes obj, arg1, ..., argN and calls obj.name_to_overload(arg1, ..., argN). Problem is, that's only usable if you've got polymorphic lambda expressions.

On 6/12/2010 5:34 PM, Mathias Gaunard wrote:
For captures, I don't see how anyone would want to do anything but capture everything by reference.
function<int(int)> plus_i( int i ) { return _1 + i; } If you capture i by reference, this code will compile but crash at runtime. Have fun debugging it! :-) -- Eric Niebler BoostPro Computing http://www.boostpro.com

Eric Niebler wrote:
On 6/12/2010 5:34 PM, Mathias Gaunard wrote:
For captures, I don't see how anyone would want to do anything but capture everything by reference.
function<int(int)> plus_i( int i ) { return _1 + i; }
If you capture i by reference, this code will compile but crash at runtime. Have fun debugging it! :-)
My main use for lambdas (be them C++0x or DSEL ones) is to pass them directly to a higher-order function or an iterator adapter; I never store them past their lifetime. I'm not sure lambdas in callbacks or signals scenarios is where they really shine.

On 6/14/10 5:59 PM, Mathias Gaunard wrote:
Eric Niebler wrote:
On 6/12/2010 5:34 PM, Mathias Gaunard wrote:
For captures, I don't see how anyone would want to do anything but capture everything by reference.
function<int(int)> plus_i( int i ) { return _1 + i; }
If you capture i by reference, this code will compile but crash at runtime. Have fun debugging it! :-)
My main use for lambdas (be them C++0x or DSEL ones) is to pass them directly to a higher-order function or an iterator adapter; I never store them past their lifetime.
I'm not sure lambdas in callbacks or signals scenarios is where they really shine.
There's a big chunk of use cases where you really want to capture by value. Spirit is one. Consider this innocent looking piece of code: int i = 10; rule<...> r = *int_[push_back(val_, _1 + i)]; By-val capture is still my preference. Otherwise, all Spirit code will be dangerously be put in the verge of collapse and we'll end up with tons of user support questions. By-val is still the way to go, after all you always have ref(i). Regards, -- Joel de Guzman http://www.boostpro.com http://spirit.sf.net

At Sat, 12 Jun 2010 22:34:28 +0100, Mathias Gaunard wrote:
Quoted out of order:
As it is, IMO it's still going to be better than the built-in lambdas.
The only real problem I have with built-in lambdas myself is the lack of polymorphic ones.
IMO it's a bad sign that explaining built-in lambdas takes more slides in my courseware than explaining Phoenix does. :-) -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Example:
int i( 1 ); ( ++phoenix::arg_names::arg1 )( i );
The result of ++phoenix::arg_names::arg1 will be 2, i will have the value 2 after the expression is evaluated.
Interesting, what will happen in this case: ( ++phoenix::arg_names::arg1 )( (const int&)10 ); Regards, Alexey Tkachenko.
participants (10)
-
Alexey Tkachenko
-
David Abrahams
-
Eric Niebler
-
Giovanni Piero Deretta
-
Joel de Guzman
-
Joel de Guzman
-
Mathias Gaunard
-
Stewart, Robert
-
Thomas Heller
-
vicente.botet