[operators] The future

Hello, I would like to gather some feedback on my plan for the future of the Boost.Operators library. As the maintainer, I have to admit it was a pretty easy job for the last years, as not much has happened. The codebase is full of work-arounds, but it seems to be pretty stable. Another member of the list recently suggested an enhancement, see <https://svn.boost.org/trac/boost/ticket/5929>, which I think shows the first of two points that I would like to straighten out in the future: 1) The library currently mixes two domains, arithmetic operators and iterator helpers 2) No rvalue-support The first point, as already mentioned in the above ticket, could be solved by separating the two parts. I imagine the future Boost.Operators library to concentrate on arithmetic operators, while another, new library could concentrate on iterator and container helpers. Maybe there is an overlap with other libraries (Boost.Iterators), but I'll leave that to others. The (new) Boost.Operators library could provide a base for implementing the iterator helpers just like the current Boost.Operators library does - while there is no dependency in the reversed direction, i.e., the Boost.Operators library does not depend on iterators. The second point is actually a strong argument for a rewrite and a different interface. I have an implementation ready and as it turns out, rvalue-support could be used to optimize some cases *only* if the type itself is commutative. This property of a type is not taken into account in the current Boost.Operators library, which is why the new implementation has different bases, including a new set of grouped operators. See the end of this post for a list of the proposed bases. Note that the new grouped operators are also a consequence of taking commutativity into account, i.e., a field as a commutative_ring+dividable. Dropping some of the old grouped operators should not be a big deal, since grouped operators are more-or-less convenience only, the real functionality is in the "Simple Arithmetic Operators" (see current documentation). If there is a strong need, we could also preserve the old implementation as <boost/legacy/operators.hpp>, but I think it's also practical to drop it completely - old Boost versions are still available for those who need them. To sum up the plan: - Concentrate on arithmetic operators - Add rvalue-support - New set of convenience/grouped operators due to rvalue-support - Drop the old implementation, iterator support Someone else (Olaf?) then takes care of the iterator helpers. So, what do you think? Regards, Daniel ----------------------------- Proposed new base classes: equality_comparable less_than_comparable totally_ordered equivalent partially_ordered addable commutative_addable subtractable subtractable_left multipliable commutative_multipliable dividable dividable_left modable modable_left ring commutative_ring field andable andable_left commutative_andable orable orable_left commutative_orable xorable xorable_left commutative_xorable bitwise bitwise_left commutative_bitwise left_shifttable right_shiftable shiftable incrementable decrementable unit_steppable

On Mon, Oct 3, 2011 at 10:37 AM, Daniel Frey <d.frey@gmx.de> wrote:
Dropping some of the old grouped operators should not be a big deal, since grouped operators are more-or-less convenience only, the real functionality is in the "Simple Arithmetic Operators" (see current documentation).
If there is a strong need, we could also preserve the old implementation as <boost/legacy/operators.hpp>, but I think it's also practical to drop it completely - old Boost versions are still available for those who need them.
Hi Daniel, Are there (no) rules for backwards compatibility? Breaking backwards compatibility should be done in steps (IMO). First, provide the new stuff. Then, deprecate the old stuff. When the new stuff is widely available (part of Debian and maybe RHEL stable for example), drop the old stuff. Old Boost versions are *not* always available. I think most (Linux) distro's ship only one version and using another version really isn't an option. Greetings, Olaf

On 10/03/2011 12:37 PM, Daniel Frey wrote:
Hello,
If there is a strong need, we could also preserve the old implementation as<boost/legacy/operators.hpp>, but I think it's also practical to drop it completely - old Boost versions are still available for those who need them.
Please, provide a deprecation period. It would be best if there was Boost.Operators v2 alongside with the current v1, which can coexist and be used in a single project. The docs should state that v1 is deprecated and provide transition guidelines for users. I think, this is the common practice for Boost libraries.

On Mon, Oct 3, 2011 at 4:37 AM, Daniel Frey <d.frey@gmx.de> wrote:
- Concentrate on arithmetic operators - Add rvalue-support - New set of convenience/grouped operators due to rvalue-support - Drop the old implementation, iterator support
Someone else (Olaf?) then takes care of the iterator helpers.
So, what do you think?
I'm very interested in this. The addition of rvalue references in C++11 makes Boost.Operators even more useful than it already is, pending it is move-aware. I have no complaints with these points and am eager to see progress. My only concern has already been stated -- prefer to not break backwards compatibility, and if you do, make the transition as easy as possible with a deprecation period. I think this goes without saying anyway, but there's no harm in stressing it. -- -Matt Calabrese

On 03/10/2011 20:51, Matt Calabrese wrote:
On Mon, Oct 3, 2011 at 4:37 AM, Daniel Frey<d.frey@gmx.de> wrote:
- Concentrate on arithmetic operators - Add rvalue-support - New set of convenience/grouped operators due to rvalue-support - Drop the old implementation, iterator support
Someone else (Olaf?) then takes care of the iterator helpers.
So, what do you think?
I'm very interested in this. The addition of rvalue references in C++11 makes Boost.Operators even more useful than it already is, pending it is move-aware. I have no complaints with these points and am eager to see progress. My only concern has already been stated -- prefer to not break backwards compatibility, and if you do, make the transition as easy as possible with a deprecation period. I think this goes without saying anyway, but there's no harm in stressing it.
What's the idea exactly? implement operator+ in terms of operator+=? That's not always suitable.

On Tue, Oct 4, 2011 at 8:02 AM, Mathias Gaunard < mathias.gaunard@ens-lyon.org> wrote:
What's the idea exactly?
implement operator+ in terms of operator+=? That's not always suitable.
Yes, it's not always suitable, but that's true of the operators in Boost.Operators already. If the definition of operators provided by Boost.Operators are not suitable for a given type, that person just shouldn't use that helper base, just as is done now. The manner in which the operators are implemented is well defined in the documentation along with requirements. The presence of r-value references only opens up more efficient implementations that can be automatically created, again, given that your type obeys certain requirements (addition must be commutative, etc.). Anyway, yes, it would implement + in terms of += just as is done now, but with r-value references you can potentially reuse one of the operands without copying it. The only question I have is should the return type be a value type or an r-value reference type (there are subtle differences for a user). A quick synopsis of what would likely be generated: type operator +( type const&, type const& ) or an equivalent with copies is defined automatically as normal type operator +( type&&, type const& ) is defined to use += on the first argument which is then move-returned type operator +( type const& left, type&& right ) is defined as return std::move( right ) + left type operator +( type&& left, type&& right ) required for disambiguation and just returns std::move( left ) + right If the operations return "type&&" instead of "type", they can potentially be more efficient, but I'm a little cautious to do that without giving it some thought because the choice implies subtle differences. I'm sure Daniel has already taken this all into consideration. -- -Matt Calabrese

on Tue Oct 04 2011, Matt Calabrese <rivorus-AT-gmail.com> wrote:
type operator +( type&& left, type&& right ) required for disambiguation and just returns std::move( left ) + right
If the operations return "type&&" instead of "type", they can potentially be more efficient, but I'm a little cautious to do that without giving it some thought because the choice implies subtle differences. I'm sure Daniel has already taken this all into consideration.
Return by value please. Otherwise: type const& x = type() + type(); std::cout << x; // BOOM; x is dangling -- Dave Abrahams BoostPro Computing http://www.boostpro.com

If the operations return "type&&" instead of "type", they can potentially be more efficient, but I'm a little cautious to do that without giving it some thought because the choice implies subtle differences. I'm sure Daniel has already taken this all into consideration.
Return by value please. Otherwise:
type const& x = type() + type(); std::cout << x; // BOOM; x is dangling
Won't the following fail too, making it completely useless? void foo(type const& x) { ... } foo(type() + type()); Regards, Nate

On 10/4/2011 12:16 PM, Nathan Ridge wrote:
If the operations return "type&&" instead of "type", they can potentially be more efficient, but I'm a little cautious to do that without giving it some thought because the choice implies subtle differences. I'm sure Daniel has already taken this all into consideration.
Return by value please. Otherwise:
type const& x = type() + type(); std::cout << x; // BOOM; x is dangling
Won't the following fail too, making it completely useless?
void foo(type const& x) { ... }
foo(type() + type());
No, because the temporary created by "type() + type()" is guaranteed to live until after foo returns. -- Eric Niebler BoostPro Computing http://www.boostpro.com

On Tue, Oct 4, 2011 at 3:22 PM, Eric Niebler <eric@boostpro.com> wrote:
No, because the temporary created by "type() + type()" is guaranteed to live until after foo returns.
This. More specifically, by the standard it will survive for the the full expression in which it appears. -- -Matt Calabrese

If the operations return "type&&" instead of "type", they can potentially be more efficient, but I'm a little cautious to do that without giving it some thought because the choice implies subtle differences. I'm sure Daniel has already taken this all into consideration.
Return by value please. Otherwise:
type const& x = type() + type(); std::cout << x; // BOOM; x is dangling
Won't the following fail too, making it completely useless?
void foo(type const& x) { ... }
foo(type() + type());
No, because the temporary created by "type() + type()" is guaranteed to live until after foo returns.
I meant in the case where operator+ returns a type&&. Then the object in question is a local variable inside operator+, which will not live past the point when operator+ returns. Of course the case when operator+ returns a type is fine. Regards, Nate

On Tue, Oct 4, 2011 at 4:52 PM, Nathan Ridge <zeratul976@hotmail.com> wrote:
I meant in the case where operator+ returns a type&&.
Then the object in question is a local variable inside operator+, which will not live past the point when operator+ returns.
No it's not, it's a reference to a passed in parameter. For instance: Type&& operator +( Type&& left, Type const& right ) { left += right; return std::move( left ); } You only return an r-value reference in the case that at least one of your parameters is an r-value reference. The reference is to that particular argument. Here, the first argument is returned by reference, not a local variable. In this way, not even a move-constructor is called (nor required) and no third object is ever created. Anyway, as was pointed out, this is probably not a good idea for other reasons, such as people expecting to be able to bind the result to a reference and have its life extended (though I suppose you could just rule-out such uses as valid when the base is used). -- -Matt Calabrese

On 4 October 2011 16:05, Matt Calabrese <rivorus@gmail.com> wrote:
Type&& operator +( Type&& left, Type const& right ) { left += right; return std::move( left ); }
Anyway, as was pointed out, this is probably not a good idea for other reasons, such as people expecting to be able to bind the result to a reference and have its life extended (though I suppose you could just rule-out such uses as valid when the base is used).
What happens with: Type t = TypeFactory(); auto sum = TypeFactory() + t; -- Nevin ":-)" Liber <mailto:nevin@eviloverlord.com> (847) 691-1404

on Tue Oct 04 2011, Nevin Liber <nevin-AT-eviloverlord.com> wrote:
On 4 October 2011 16:05, Matt Calabrese <rivorus@gmail.com> wrote:
Type&& operator +( Type&& left, Type const& right ) { left += right; return std::move( left ); }
Anyway, as was pointed out, this is probably not a good idea for other reasons, such as people expecting to be able to bind the result to a reference and have its life extended (though I suppose you could just rule-out such uses as valid when the base is used).
What happens with:
Type t = TypeFactory(); auto sum = TypeFactory() + t;
That one's fine; sum is deduced to have a non-reference type. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Tue, Oct 4, 2011 at 8:38 AM, Matt Calabrese <rivorus@gmail.com> wrote:
type operator +( type const&, type const& ) or an equivalent with copies is defined automatically as normal
type operator +( type&&, type const& ) is defined to use += on the first argument which is then move-returned
type operator +( type const& left, type&& right ) is defined as return std::move( right ) + left
type operator +( type&& left, type&& right ) required for disambiguation and just returns std::move( left ) + right
If we're going for C++11, I think the correct way to do this is with a single function: type operator+(type lhs, const type& rhs) { lhs += rhs; return lhs; } That should, given that the class implements move-semantics, be sufficient. -- GMan, Nick Gorski

on Tue Oct 04 2011, GMan <gmannickg-AT-gmail.com> wrote:
On Tue, Oct 4, 2011 at 8:38 AM, Matt Calabrese <rivorus@gmail.com> wrote:
type operator +( type const&, type const& ) or an equivalent with copies is defined automatically as normal
type operator +( type&&, type const& ) is defined to use += on the first argument which is then move-returned
type operator +( type const& left, type&& right ) is defined as return std::move( right ) + left
type operator +( type&& left, type&& right ) required for disambiguation and just returns std::move( left ) + right
If we're going for C++11, I think the correct way to do this is with a single function:
type operator+(type lhs, const type& rhs) { lhs += rhs; return lhs; }
That should, given that the class implements move-semantics, be sufficient.
With that implementation x + (y + z) will make unnecessary copies. This is a solved problem. You need 4 overloads to handle all the cases without ambiguity. See the rvalue reference proposals for detailed examples. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Tue, Oct 4, 2011 at 6:01 PM, GMan <gmannickg@gmail.com> wrote:
If we're going for C++11, I think the correct way to do this is with a single function:
type operator+(type lhs, const type& rhs) { lhs += rhs; return lhs; }
That should, given that the class implements move-semantics, be sufficient.
If you do that then what does the version look like if the right hand operand is the moved-from operand? If you do: type operator+( const type& lhs, type rhs ) { /*...*/ } Then neither one of the two overloads is a better match over the other, regardless of which and how many operands are r-values as opposed to l-values. Being explicit with r-value references and l-value references disambiguates so that there is no problem. It is interesting to note that IIRC Boost.Operators already does do the value-type for operand form, at least on certain compilers, meaning that current Boost.Operators is already partially move-aware to some extent. -- -Matt Calabrese

On 4 October 2011 17:38, Matt Calabrese <rivorus@gmail.com> wrote:
On Tue, Oct 4, 2011 at 6:01 PM, GMan <gmannickg@gmail.com> wrote:
If we're going for C++11, I think the correct way to do this is with a single function:
type operator+(type lhs, const type& rhs) { lhs += rhs; return lhs; }
That should, given that the class implements move-semantics, be sufficient.
If you do that then what does the version look like if the right hand operand is the moved-from operand? If you do:
type operator+( const type& lhs, type rhs ) { /*...*/ }
You are assuming that the user intends operator+ to be commutative. The user will need some way to specify it as they can if the type of lhs and rhs are different, and if that assumption is the default, it may break existing code. -- Nevin ":-)" Liber <mailto:nevin@eviloverlord.com> (847) 691-1404

On Tue, Oct 4, 2011 at 6:50 PM, Nevin Liber <nevin@eviloverlord.com> wrote:
You are assuming that the user intends operator+ to be commutative.
Right, that's one of the requirements we mentioned earlier. The OP mentions, for instance: addable commutative_addable So in other words we wouldn't be requiring or relying on commutativity in all cases, we'd just be providing more powerful bases in addition to the old ones. The user will need some way to specify it as they can if the type of
lhs and rhs are different, and if that assumption is the default, it may break existing code.
Yeah, I think separately named templates that have "commutative" in the name is the easiest way to handle this. -- -Matt Calabrese
participants (10)
-
Andrey Semashev
-
Daniel Frey
-
Dave Abrahams
-
Eric Niebler
-
GMan
-
Mathias Gaunard
-
Matt Calabrese
-
Nathan Ridge
-
Nevin Liber
-
Olaf van der Spek