[operators] A modern SFINAE-based version of boost::operators?
Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me: Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach. For example struct Me : make_operators_for<Me>{ Me& operator+=(Me const&); // You get + bool operator<(Me const&) const; // You get all relops (<=> will make that obsolete) Me& operator++(); // you get postfix //etc. }; Today we have the facilities and compilers to make that happen. What do you think? Who should I ask? Anyone doing any work on operators or have any thoughts about updating boost::operators? --Beman
I for one would be grateful for work on this. Particularly if it also
automatically manufactures move-aware versions of binary operators.
eg:
Foo&& operator+(Foo&& l, Foo const& r) { return std::move(l += r); }
Foo operator+(Foo const& l, Foo const& r) { return l += r; return l; }
On 13 November 2017 at 15:20, Beman Dawes via Boost
Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
For example
struct Me : make_operators_for<Me>{ Me& operator+=(Me const&); // You get + bool operator<(Me const&) const; // You get all relops (<=> will make that obsolete) Me& operator++(); // you get postfix //etc. };
Today we have the facilities and compilers to make that happen.
What do you think? Who should I ask?
Anyone doing any work on operators or have any thoughts about updating boost::operators?
--Beman
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/ mailman/listinfo.cgi/boost
of course I mean:
Foo operator+(Foo l, Foo const& r) { return l += r; return l; }
in the second version.
On 13 November 2017 at 16:05, Richard Hodges
I for one would be grateful for work on this. Particularly if it also automatically manufactures move-aware versions of binary operators.
eg:
Foo&& operator+(Foo&& l, Foo const& r) { return std::move(l += r); } Foo operator+(Foo const& l, Foo const& r) { return l += r; return l; }
On 13 November 2017 at 15:20, Beman Dawes via Boost
wrote:
Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
For example
struct Me : make_operators_for<Me>{ Me& operator+=(Me const&); // You get + bool operator<(Me const&) const; // You get all relops (<=> will make that obsolete) Me& operator++(); // you get postfix //etc. };
Today we have the facilities and compilers to make that happen.
What do you think? Who should I ask?
Anyone doing any work on operators or have any thoughts about updating boost::operators?
--Beman
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman /listinfo.cgi/boost
On 11/13/17 6:20 AM, Beman Dawes via Boost wrote:
Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
For example
struct Me : make_operators_for<Me>{ Me& operator+=(Me const&); // You get + bool operator<(Me const&) const; // You get all relops (<=> will make that obsolete) Me& operator++(); // you get postfix //etc. };
Today we have the facilities and compilers to make that happen.
What do you think? Who should I ask?
Anyone doing any work on operators or have any thoughts about updating boost::operators?
I'm pretty familiar with this. I used boost operators to create BOOST_STRONG_TYPE which a number of people liked. As time went on, and I wanted to make the serialization library more bullet proof, I fell out of love with it because it generated all the numeric operators, thus admitting errors (such as multiplying two object ids) I would have preferred to be trapped at compile time. So I eventually removed the usage of it. This kind of thinking eventually has led me to my current major project and likely last hurrah: my "Algebra" library. This was the subject of my presentation at CppCon 2016 “C++, Abstract Algebra and Practical Applications" https://www.youtube.com/watch?v=632a-DMM5J0 I spent significant amount of time looking at boost operators and other similar ideas for "automatically" adding "missing" operators. The problem which arises is trying to automatically determine which operators are missing. In the presentation I used the example of a quantity length. Given a length type and a corresponding + operator it makes sense to "generate" a += operator. But what about - ? one can take the difference of two lengths - but that operation is not guaranteed to yield a valid value for length - so the automatic operations wouldn't be a good choice. A similar type - Position - would have no problem with an automatically generated - operator. So I concluded that automatically generating "related" operators would turn in to a minefield littered with lots of special cases, exceptions, weird rules and a documentation nightmare. I've turned to the implementation of concepts of abstract algebra in terms of C++ as way to systematize the selection, declaration and definition of operators in C++ for specific types. It is currently a work in progress. The ultimate goal is to be able to specify algebraic types with a set of attributes which would create all appropriate operators while trapping any inappropriate usage. Such a facility, in conjunction with Boost Units (enhanced with improved easier to use documentation and perhaps some corresponding tweaks) would provide the base for writing provably correct programs. These programs would be written in terms of these strongly defined types such that it is almost impossible to get wrong. (of course these would be a bitch to compile - writing then normal crappy code is really fast and fun after all). An offshoot of this, which has been incredibly absorbing, is the safe numerics library whose implemenation requires some of the ideas mentioned above. This is proceeding apace - it has required more time and effort than anticipated. Robert Ramey
On 13. Nov 2017, at 15:20, Beman Dawes via Boost
wrote: Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
I'm the current maintainer of Boost.Operators and I heard about anyone working on that.
A few thoughts from my side:
I worked on other improvements, which ultimately went into its own library: https://github.com/taocpp/operators. There, I distinguish between commutative and non-commutative versions of some operators, as they allow me to provide additional optimisations (leading to fewer temporaries). This could not be done with a detection-style approach as the presence of an operator+ will not tell you whether or not it is commutative. It also plays some tricks returning rvalue-references which some people don't like (or consider dangerous), so it's not suitable for Boost.Operators.
Leaving that aside, I'd be willing to accept an *additional* base class for Boost.Operators just like you outlined. This *might* go into its own header as the current Boost.Operators library supports some quite ancient compilers and I don't want to break people's code. It also allows users to fall back to the existing approach if necessary, for example if the SFINAE-based code is too slow to compile. Or if you simply need more control of what exactly will be generated.
However, there will be a few questions: Shall we generate == if only < and > are provided? What about additional types? Think: x < 0 - shall we detect/generate them as well? Maybe something like this:
struct Me : make_operators_for
On 13. Nov 2017, at 17:26, Daniel Frey via Boost
wrote: On 13. Nov 2017, at 15:20, Beman Dawes via Boost
wrote: Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
I'm the current maintainer of Boost.Operators and I heard about anyone working on that.
Above sentence is missing a "haven't": I haven't heard of anyone working on that.
I am working on a proposal for the next committee meeting that would generate more of these operators. We recently voted in a feature for the comparison operators: the user defines `operator<=>` and gets all current comparison operators generated automatically. I will be sure to post my paper to this list for feedback prior to the mailing deadline. My paper will include a fairly comprehensive rationale for the design, which will be different from Boost.Operators. It has the advantage of needing only to work with code built against the latest standard, and it has language support, and it has the "disadvantage" of being done by default instead of being explicitly requested by the user, which obviously has slightly different design constraints. On Mon, Nov 13, 2017 at 9:46 AM, Daniel Frey via Boost < boost@lists.boost.org> wrote:
On 13. Nov 2017, at 17:26, Daniel Frey via Boost
wrote: On 13. Nov 2017, at 15:20, Beman Dawes via Boost
wrote: Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
I'm the current maintainer of Boost.Operators and I heard about anyone working on that.
Above sentence is missing a "haven't": I haven't heard of anyone working on that.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/ mailman/listinfo.cgi/boost
Le 14/11/2017 à 02:38, David Stone via Boost a écrit :
I am working on a proposal for the next committee meeting that would generate more of these operators. We recently voted in a feature for the comparison operators: the user defines `operator<=>` and gets all current comparison operators generated automatically. I will be sure to post my paper to this list for feedback prior to the mailing deadline. My paper will include a fairly comprehensive rationale for the design, which will be different from Boost.Operators. It has the advantage of needing only to work with code built against the latest standard, and it has language support, and it has the "disadvantage" of being done by default instead of being explicitly requested by the user, which obviously has slightly different design constraints. Hi,
I'm curious on knowing more on your approach. Note that the operator<==> return something that states what kind of order we want to define, and in this sense this is an explicit mapping. I like the explicit mapping and find the implicit one could be a source of surprises. I'm realy interested on the derivation of functions from other in general. If you need a reader with a another point of view, please, contact me. Best, Vicente
Here is the initial wording of my proposal for the C++ standardization
committee on generating more operators by default. I welcome any feedback.
I intend to present this at the next meeting in March.
https://github.com/davidstone/isocpp/blob/master/generate-operators.md
On Mon, Nov 13, 2017 at 8:38 PM, David Stone
I am working on a proposal for the next committee meeting that would generate more of these operators. We recently voted in a feature for the comparison operators: the user defines `operator<=>` and gets all current comparison operators generated automatically. I will be sure to post my paper to this list for feedback prior to the mailing deadline. My paper will include a fairly comprehensive rationale for the design, which will be different from Boost.Operators. It has the advantage of needing only to work with code built against the latest standard, and it has language support, and it has the "disadvantage" of being done by default instead of being explicitly requested by the user, which obviously has slightly different design constraints.
On 19. Nov 2017, at 06:22, David Stone via Boost
wrote: Here is the initial wording of my proposal for the C++ standardization committee on generating more operators by default. I welcome any feedback. I intend to present this at the next meeting in March.
https://github.com/davidstone/isocpp/blob/master/generate-operators.md
Arithmetic operators (especially + and *) can be commutative, giving you very different (and more efficient) implementations. In fact, it is my feeling that commutative + and * are more common than the non-commutative versions (even implied by calling them "arithmetic"). Your proposal completely ignores that (and I don't see how you'd be able to specify if you want a commutative or non-commutative version). Also, picking std::string::operator+ is a bad example. It is too special to be the template for a general implementation, not only because it is non-commutative but also because classes generally don't have capacity() or other similar properties which should play a role on how to overload operators. Even if we were to agree that your implementation is "ideal" for this specific case, you could just use that implementation for std::string specifically without any additional support from the language. And if you want to argue that there are more, similar enough cases like that this would only speak for a library-based solution (like Boost.Operators or taocpp/operators) to allow different variants to co-exist and be explicitly named by the library author and picked by the user. Language support must be universal enough to cover 90% (or even 99%) of the use-cases - your proposal fails to do that.
Le 19/11/2017 à 06:22, David Stone via Boost a écrit :
Here is the initial wording of my proposal for the C++ standardization committee on generating more operators by default. I welcome any feedback. I intend to present this at the next meeting in March.
https://github.com/davidstone/isocpp/blob/master/generate-operators.md
Hi David, thanks for sharing. I believe that we shouldn't generate operations individually. We could generate some operations given a minimal definition set for a specific concept. As you note on your paper C++ operators are overloaded on different concepts. We need the concept in order to know what do we want to generate. I have a lot of cases where operator += must be derived of operator+. But I have also cases where the compiler could generate an optimal code defining operator + in function of operator+=. When we define operator +, very often we want also operator++. And sometime we don't want it. I'm not for having a language that generates more functions by default without opt-in. It is quite complex for a lot of people to imagine what they don't see. I want to see in the code that we are asking for the derivation of some operations given we provide some other. This is what Haskell deriving construct is for. With Template Haskell you can describe how this operation are derived. This is something we could have with the current direction of meta programming. I believe the paper is missing concrete examples that could apply to the standard, and seen the operators that could be derived in each case and with which semantic. I agree with the comments of Daniel Frey. operator+ for strings i snot the good example, as it is not commutative. see operator++ as x+= 1 need a value 1 and seen -a as 0 - a needs a value 0. These have a sense on certain kind of types. So the solution consists in identifying the kind of types where this could be applied. |a - b| is theoretically equivalent to |a + -b only when b define -b without overflow, isn't it? Could you elaborate on the following sentence "||Even if that negation is well defined, there still may be overflow; consider |ptr - 1U|: we rewrote that to be |ptr + -1U|, the pointer arithmetic would overflow." |Could you show some examples (in the standard) where seen |lhs->rhs| as |(*lhs).rhs would reduce the standard wording? Some additional non-standard examples?| I don't see a flat map as a random access container, but maybe I'm missing something evident. Note that operator <=> return type implies the operation we want to derive and its implementation. While I find the approach interesting, I believe also it is a hack, it doesn't scale. We need a more generic mechanism to generate operations. On what concern the original post, and waiting for meta in the language (reflection/reification), we can define those using CRTP in Boost. But I will be against doing it without associating them to a concept. If we replace the spaceship operator <==> by a `order::compare` function we are able to define using CRTP the associated operations. We don't need a language change. Best, Vicente
Dear Daniel,
On 13. Nov 2017, at 17:26, Daniel Frey via Boost
wrote: I worked on other improvements, which ultimately went into its own library: https://github.com/taocpp/operators. There, I distinguish between commutative and non-commutative versions of some operators, as they allow me to provide additional optimisations (leading to fewer temporaries). This could not be done with a detection-style approach as the presence of an operator+ will not tell you whether or not it is commutative. It also plays some tricks returning rvalue-references which some people don't like (or consider dangerous), so it's not suitable for Boost.Operators.
when I was looking into operators for the histogram library, I also considered Boost.Operators and I also looked into your taocpp/operators. I was wondering why are the rvalue-optimised versions of operators are not already part of Boost.Operators? I don't know what kind of tricks you are talking about, surely there must be rvalue-enabled versions that are standard compliant and safe, like the implementations that Richard Hodges mentioned. This seems like a straight-forward improvement of Boost.Operators that doesn't break old code. Or am I missing something? Best regards, Hans
On 14. Nov 2017, at 09:43, Hans Dembinski
wrote: Dear Daniel,
On 13. Nov 2017, at 17:26, Daniel Frey via Boost
wrote: I worked on other improvements, which ultimately went into its own library: https://github.com/taocpp/operators. There, I distinguish between commutative and non-commutative versions of some operators, as they allow me to provide additional optimisations (leading to fewer temporaries). This could not be done with a detection-style approach as the presence of an operator+ will not tell you whether or not it is commutative. It also plays some tricks returning rvalue-references which some people don't like (or consider dangerous), so it's not suitable for Boost.Operators.
when I was looking into operators for the histogram library, I also considered Boost.Operators and I also looked into your taocpp/operators.
I was wondering why are the rvalue-optimised versions of operators are not already part of Boost.Operators? I don't know what kind of tricks you are talking about, surely there must be rvalue-enabled versions that are standard compliant and safe, like the implementations that Richard Hodges mentioned. This seems like a straight-forward improvement of Boost.Operators that doesn't break old code. Or am I missing something?
The "problem" is that returning rvalue-references will lead to dangling references in two cases. Before looking at those cases, consider a commutative operator+ (abbreviated version): T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T&& operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); } // v2 T&& operator+( const T& lhs, T&& rhs ) { rhs += lhs; return std::move( rhs ); } // v3, remember: we assume a commutative + T&& operator+( T&& lhs, T&& rhs ) { lhs += rhs; return std::move( lhs ); } // v4 now consider using it: const T t1, t2, t3, t4; // some values const T t = t1 + ( t2 + t3 ) + t4; The last line creates a temporary T for t2+t3 with v1, then calls v3 which calls += on the temporary, then v2 which again calls += on the temporary. Finally, the temporary is moved into t. So far, so good: Intermediate temporaries are only destroyed at the end of a full expression. This also means, that calling a function will work as expected. Given: void f( const T& ); // some function which wants above T calling f( t1 + ( t2 + t3 ) + t4 ); // fine, creates a single temporary works as it should. Now for the two cases where you can create a dangling reference: a) Explicitly binding to a reference "to prolong the lifetime of the temporary". This happens if you write const T& t = t1 + ( t2 + t3 ) + t4; As the last operator+ return an rvalue reference instead of an rvalue, the "const T& t =" does not bind to a temporary. The intermediate temporary created by (v2+v3) is destroyed at the end of the expression, hence t is now a dangling reference. Oops. This case is explicit, so you were basically asking for it. In my opinion it is user-error, since you expected a temporary but you haven't checked. Some people disagree and say that every class must make sure that this always works - basically forcing methods (especially operators) to always return an ralue / a temporary. This would effectively disallow these optimisations. As I can not know whether people used this in existing code, it is also not backwards compatible. This is the reason why I decided to put this into a new library and kept it out of Boost.Operators. b) The second case is implicit: Using a range-based for loop. Consider T being a container (or container-like) class and: for ( const auto& element : t1 + ( t2 + t3 ) + t4 ) { ... } This will also not work as the intermediate temporary created by (t2+t3) and which is passed to the range-based for loop is destroyed *before* the loop is executed. A solution is to actually store the value first: const auto t = t1 + ( t2 + t3 ) + t4; for ( const auto& element : t ) { ... } or maybe with C++20: for ( const auto t = t1 + ( t2 + t3 ) + t4; const auto& element : t ) { ... } // Yuck. Why?? I still think that a range-based for loop should treat the expression similar to a function call, the intermediate temporaries should only be destroyed at the end of the full loop. Sadly, all my attempts to convince others lead to nowhere. I hope this explains what I have done, why I chose to put it in its own library and not into Boost.Operators and what is controversial about it. Best regards, Daniel
On 11/14/2017 1:16 PM, Daniel Frey via Boost wrote:
On 14. Nov 2017, at 09:43, Hans Dembinski
wrote: Dear Daniel,
On 13. Nov 2017, at 17:26, Daniel Frey via Boost
wrote: I worked on other improvements, which ultimately went into its own library: https://github.com/taocpp/operators. There, I distinguish between commutative and non-commutative versions of some operators, as they allow me to provide additional optimisations (leading to fewer temporaries). This could not be done with a detection-style approach as the presence of an operator+ will not tell you whether or not it is commutative. It also plays some tricks returning rvalue-references which some people don't like (or consider dangerous), so it's not suitable for Boost.Operators.
when I was looking into operators for the histogram library, I also considered Boost.Operators and I also looked into your taocpp/operators.
I was wondering why are the rvalue-optimised versions of operators are not already part of Boost.Operators? I don't know what kind of tricks you are talking about, surely there must be rvalue-enabled versions that are standard compliant and safe, like the implementations that Richard Hodges mentioned. This seems like a straight-forward improvement of Boost.Operators that doesn't break old code. Or am I missing something?
The "problem" is that returning rvalue-references will lead to dangling references in two cases.
Before looking at those cases, consider a commutative operator+ (abbreviated version):
T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T&& operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); } // v2 T&& operator+( const T& lhs, T&& rhs ) { rhs += lhs; return std::move( rhs ); } // v3, remember: we assume a commutative + T&& operator+( T&& lhs, T&& rhs ) { lhs += rhs; return std::move( lhs ); } // v4
My understanding may be deficient but I would never assume that adding two values would change either of the values in any way, even if one or more of those values were rvalue references. Please tell me what I am missing and I will gladly admit my C++ ignorance in this matter if that is the case. snipped...
My understanding may be deficient but I would never assume that adding two values would change either of the values in any way, even if one or more of those values were rvalue references. Please tell me what I am missing and I will gladly admit my C++ ignorance in this matter if that is the case.
If you pass in an rvalue reference, you *do* expect it to be modified and you do not expect it to be accessible afterwards. Given: void f( std::vector<int>&& v ) { ... } and calling std::vector< int > v = ...; f(v); why do you expect that v has not been changed or that you can still use it? Even "adding" the values doesn't change that. If you give me an rvalue reference, I'm (mostly) free to destroy it in any way I like :) And yes, in a moved-from state you are still allowed to re-assign the value. So, I think I am expecting users of those operators (taocpp/operators) to use value-semantics from a user's perspective. You don't write int r = std::move(1) + std::move(2); just because you don't need 1 and 2 afterwards.
On 11/14/2017 2:35 PM, Daniel Frey via Boost wrote:
My understanding may be deficient but I would never assume that adding two values would change either of the values in any way, even if one or more of those values were rvalue references. Please tell me what I am missing and I will gladly admit my C++ ignorance in this matter if that is the case.
If you pass in an rvalue reference, you *do* expect it to be modified and you do not expect it to be accessible afterwards.
OK, Point conceded.
Given:
void f( std::vector<int>&& v ) { ... }
and calling
std::vector< int > v = ...; f(v);
I think you meant f(std::move(v));
why do you expect that v has not been changed or that you can still use it?
Agreed.
Even "adding" the values doesn't change that. If you give me an rvalue reference, I'm (mostly) free to destroy it in any way I like :)
Ok. It does seem odd to "change" a value passed in an addition, but I do see that changing an rvalue reference should be allowable, since it is a temporary.
And yes, in a moved-from state you are still allowed to re-assign the value. So, I think I am expecting users of those operators (taocpp/operators) to use value-semantics from a user's perspective. You don't write
int r = std::move(1) + std::move(2); just because you don't need 1 and 2 afterwards.
Agreed.
On 14. Nov 2017, at 22:32, Edward Diener via Boost
wrote: On 11/14/2017 2:35 PM, Daniel Frey via Boost wrote:
My understanding may be deficient but I would never assume that adding two values would change either of the values in any way, even if one or more of those values were rvalue references. Please tell me what I am missing and I will gladly admit my C++ ignorance in this matter if that is the case. If you pass in an rvalue reference, you *do* expect it to be modified and you do not expect it to be accessible afterwards.
OK, Point conceded.
Given: void f( std::vector<int>&& v ) { ... } and calling std::vector< int > v = ...; f(v);
I think you meant
f(std::move(v));
D'oh! Of course. Thanks for reading what I meant instead of reading what I wrote :)
If you pass in an rvalue reference, you *do* expect it to be modified and you do not expect it to be accessible afterwards.
That's only an until-so-far STL convention.
In my own code I treat rvalue references as "this may or may not be
consumed" parameter. So the function may consume the input wholly,
leaving it in some zombie state. Or it may leave it as is. Obviously the
return type says what happened.
This lets you write filtering functions such as:
SomeObj foo.
filter_a(std::move(foo));
filter_b(std::move(foo));
filter_c(std::move(foo));
filter_d(std::move(foo));
Now some will quite rightly object to using std::move when I mean "maybe
move". I should really type out:
filter_a(static_cast
On 15. Nov 2017, at 01:35, Niall Douglas via Boost
wrote: If you pass in an rvalue reference, you *do* expect it to be modified and you do not expect it to be accessible afterwards.
That's only an until-so-far STL convention.
In my own code I treat rvalue references as "this may or may not be consumed" parameter. So the function may consume the input wholly, leaving it in some zombie state. Or it may leave it as is. Obviously the return type says what happened.
OK, I should have said you do have to expect that the value might be modified. But in the general case, you won't know if any function taking an rvalue-reference will or will not modify / move it. Only if it is explicitly documented (or if you have control over it), you can know. And documentation is not checked by the compiler.
This lets you write filtering functions such as:
SomeObj foo. filter_a(std::move(foo)); filter_b(std::move(foo)); filter_c(std::move(foo)); filter_d(std::move(foo));
For this example this might work, but you can not use this kind of thinking in general or force it on anyone.
In case anyone thinks I am being weird on this, I was taught to think of rvalue ref parameter inputs this way by Howard Hinnant. It's been a useful technique to know. I just wish clang-tidy didn't warn on this technique so readily.
I wonder why you don't just pass in a lvalue-reference. You can still modify the value in one of those functions and the remaining state should be visible to the following functions just like before, no?
On 15/11/2017 07:16, Daniel Frey wrote:
Before looking at those cases, consider a commutative operator+ (abbreviated version):
T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T&& operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); } // v2 T&& operator+( const T& lhs, T&& rhs ) { rhs += lhs; return std::move( rhs ); } // v3, remember: we assume a commutative + T&& operator+( T&& lhs, T&& rhs ) { lhs += rhs; return std::move( lhs ); } // v4
Given that NRVO is mandatory now, isn't it just as efficient and more correct to declare these as: T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T operator+( T&& lhs, const T& rhs ) { lhs += rhs; return lhs; } // v2 T operator+( const T& lhs, T&& rhs ) { rhs += lhs; return rhs; } // v3, remember: we assume a commutative + T operator+( T&& lhs, T&& rhs ) { lhs += rhs; return lhs; } // v4 (You could save a little typing by using "return lhs += rhs;" but that's probably not a good idea as you're left to the whims of compiler inlining whether it triggers some kind of RVO or not.) Unless I'm missing something?
a) Explicitly binding to a reference "to prolong the lifetime of the temporary".
This happens if you write
const T& t = t1 + ( t2 + t3 ) + t4;
As the last operator+ return an rvalue reference instead of an rvalue, the "const T& t =" does not bind to a temporary. The intermediate temporary created by (v2+v3) is destroyed at the end of the expression, hence t is now a dangling reference. Oops.
The above would fix this, and your other case.
On 15/11/2017 19:27, Gevorg Voskanyan wrote:
On 11/15/17 03:04, Gavin Lambert wrote:
Given that NRVO is mandatory now, <snip>
It is? Since when? AFAIK C++17 makes RVO mandatory, but not NRVO.
True, I was thinking of a different case and misspoke. The rest of the post still stands though; any optimiser worth its salt should be able to apply NVRO there and elide most if not all of the copies, and it should be safer without sacrificing performance. Granted that I have not actually measured this, and so might end up surprised.
Dear Gavin,
On 15. Nov 2017, at 07:58, Gavin Lambert via Boost
wrote: On 15/11/2017 19:27, Gevorg Voskanyan wrote:
On 11/15/17 03:04, Gavin Lambert wrote:
Given that NRVO is mandatory now, <snip> It is? Since when? AFAIK C++17 makes RVO mandatory, but not NRVO.
True, I was thinking of a different case and misspoke. The rest of the post still stands though; any optimiser worth its salt should be able to apply NVRO there and elide most if not all of the copies, and it should be safer without sacrificing performance.
Granted that I have not actually measured this, and so might end up surprised.
I played around with the two versions of operators, the one returning rvalue references and the ones you suggested that might apply NVRO. https://github.com/HDembinski/testing_grounds/blob/master/test/test_fast_ope... https://github.com/HDembinski/testing_grounds/blob/master/test/test_fast_ope... On Apple Clang 9.0.0, NVRO does not seem to work, but perhaps I am not tracking this correctly. I use this class to track copying calls: static unsigned ctor_count = 0; struct A { A() { ++ctor_count; } A(const A&) { ++ctor_count; } A& operator=(const A&) { ++ctor_count; return *this; } A(A&&) {} A& operator=(A&&) { return *this; } A& operator+=(const A&) { return *this; } }; Best regards, Hans
On 15. Nov 2017, at 00:04, Gavin Lambert via Boost
wrote: On 15/11/2017 07:16, Daniel Frey wrote:
Before looking at those cases, consider a commutative operator+ (abbreviated version): T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T&& operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); } // v2 T&& operator+( const T& lhs, T&& rhs ) { rhs += lhs; return std::move( rhs ); } // v3, remember: we assume a commutative + T&& operator+( T&& lhs, T&& rhs ) { lhs += rhs; return std::move( lhs ); } // v4
Given that NRVO is mandatory now, isn't it just as efficient and more correct to declare these as:
T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T operator+( T&& lhs, const T& rhs ) { lhs += rhs; return lhs; } // v2 T operator+( const T& lhs, T&& rhs ) { rhs += lhs; return rhs; } // v3, remember: we assume a commutative + T operator+( T&& lhs, T&& rhs ) { lhs += rhs; return lhs; } // v4
(You could save a little typing by using "return lhs += rhs;" but that's probably not a good idea as you're left to the whims of compiler inlining whether it triggers some kind of RVO or not.)
Unless I'm missing something?
You are confusing optimising away copy-operations with optimising away the temporaries altogether. Think of a class that does not benefit from move, e.g. a small matrix class holding its values directly.
a) Explicitly binding to a reference "to prolong the lifetime of the temporary". This happens if you write const T& t = t1 + ( t2 + t3 ) + t4; As the last operator+ return an rvalue reference instead of an rvalue, the "const T& t =" does not bind to a temporary. The intermediate temporary created by (v2+v3) is destroyed at the end of the expression, hence t is now a dangling reference. Oops.
The above would fix this, and your other case.
Yes, but at a cost. More moves, which become copies if the class can not benefit from move. Even if it can, moves still do have *some* cost, removing the temporaries completely eliminates that cost.
For those interested, here's a test program with which you can play. The results are, respectively, 0+1, 3+0, 0+3, 0+3, 0+0. #include <iostream> #include <utility> int copies = 0; int moves = 0; struct T { int v_; explicit T( int v ): v_( v ) {} T( T const& t ): v_( t.v_ ) { ++copies; } T( T&& t ): v_( t.v_ ) { ++moves; } T& operator+=( T const& t ) { v_ += t.v_; return *this; } }; T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T&& operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move( lhs ); } // v2 T&& operator+( const T& lhs, T&& rhs ) { rhs += lhs; return std::move( rhs ); } // v3, remember: we assume a commutative + T&& operator+( T&& lhs, T&& rhs ) { lhs += rhs; return std::move( lhs ); } // v4 /* T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T operator+( T&& lhs, const T& rhs ) { lhs += rhs; return lhs; } // v2 T operator+( const T& lhs, T&& rhs ) { rhs += lhs; return rhs; } // v3, remember: we assume a commutative + T operator+( T&& lhs, T&& rhs ) { lhs += rhs; return lhs; } // v4 */ /* T operator+( const T& lhs, const T& rhs ) { T nrv( lhs ); nrv += rhs; return nrv; } // v1 T operator+( T&& lhs, const T& rhs ) { lhs += rhs; return std::move(lhs); } // v2 T operator+( const T& lhs, T&& rhs ) { rhs += lhs; return std::move(rhs); } // v3, remember: we assume a commutative + T operator+( T&& lhs, T&& rhs ) { lhs += rhs; return std::move(lhs); } // v4 */ /* T operator+( T lhs, const T& rhs ) { lhs += rhs; return lhs; } */ /* T operator+( T const & lhs, T const & rhs ) { return T( lhs.v_ + rhs.v_ ); } */ int main() { T t = T(1) + T(2) + T(3) + T(4); std::cout << "T(" << t.v_ << "): " << copies << " copies, " << moves << " moves\n"; }
On 15. Nov 2017, at 17:05, Peter Dimov via Boost
wrote: For those interested, here's a test program with which you can play. The results are, respectively, 0+1, 3+0, 0+3, 0+3, 0+0.
Nice, thanks for that. But the 0+0 is slightly misleading, as you don't copy the value with a copy-ctor, but you initialise a new value directly - this should count as well, especially thinking of cases like a matrix-like class with, say, an array of NxM elements as a direct member.
Daniel Frey wrote:
On 15. Nov 2017, at 17:05, Peter Dimov via Boost
wrote: For those interested, here's a test program with which you can play. The results are, respectively, 0+1, 3+0, 0+3, 0+3, 0+0.
Nice, thanks for that. But the 0+0 is slightly misleading, as you don't copy the value with a copy-ctor, but you initialise a new value directly - this should count as well, especially thinking of cases like a matrix-like class with, say, an array of NxM elements as a direct member.
True, it does not count the constructions. These are 4 for the += based variations and 7 for the plain op+. It's all slightly misleading anyway, because for the small matrix case the copy/move constructors don't actually have side effects and therefore get optimized out; they are only relevant in the case of something like std::string where copy/move aren't defaulted. Also, the results for T t1(1), t2(2), t3(3), t4(4); T t = t1 + t2 + t3 + t4; are slightly different. 4+1+1, 4+3+0, 4+1+2, 4+1+3, 7+0+0.
On 16/11/2017 05:41, Peter Dimov wrote:
It's all slightly misleading anyway, because for the small matrix case the copy/move constructors don't actually have side effects and therefore get optimized out; they are only relevant in the case of something like std::string where copy/move aren't defaulted.
That was my point; your test code doesn't test copy elision because your constructors have side effects, so can't be elided. Of course, if you remove the counting side effects in that code then the compiler just inlines everything to a single mov constant 10 :) FWIW, in VC14.0 if you force it to use external parameters it can't inline away then this code: __declspec(noinline) T calc(int a, int b, int c, int d) { return T(a) + T(b) + T(c) + T(d); } Turns into this with the rvalue-ref-return operators: x86: ; _a$ = edx 00003 03 55 08 add edx, DWORD PTR _b$[ebp] 00006 03 55 0c add edx, DWORD PTR _c$[ebp] 00009 8b 45 10 mov eax, DWORD PTR _d$[ebp] 0000c 03 c2 add eax, edx 0000e 89 01 mov DWORD PTR [ecx], eax 00010 8b c1 mov eax, ecx x64: ; _a$ = edx ; _b$ = r8d ; _c$ = r9d 00000 41 03 d0 add edx, r8d 00003 48 8b c1 mov rax, rcx 00006 41 03 d1 add edx, r9d 00009 03 54 24 28 add edx, DWORD PTR d$[rsp] 0000d 89 11 mov DWORD PTR [rcx], edx Whereas with the value-return operators: x86: ; _a$ = edx 00003 8b 45 08 mov eax, DWORD PTR _b$[ebp] 00006 03 c2 add eax, edx 00008 03 45 0c add eax, DWORD PTR _c$[ebp] 0000b 03 45 10 add eax, DWORD PTR _d$[ebp] 0000e 89 01 mov DWORD PTR [ecx], eax 00010 8b c1 mov eax, ecx x64: ; _a$ = edx ; _b$ = r8d ; _c$ = r9d 00000 42 8d 04 02 lea eax, DWORD PTR [rdx+r8] 00004 41 03 c1 add eax, r9d 00007 03 44 24 28 add eax, DWORD PTR d$[rsp] 0000b 89 01 mov DWORD PTR [rcx], eax 0000d 48 8b c1 mov rax, rcx The rvalue versions are very slightly more efficient, it looks like, although they're pretty similar (and it's even sneaky enough to turn one of the adds into an lea in the last one). Though, of course, counting assembly ops means little with modern CPUs, so take that with a grain of salt. And again granted something bigger than an int or with non-trivial copy constructors will get different results, but overall it looks like I was wrong with my initial supposition.
On 16. Nov 2017, at 02:39, Gavin Lambert via Boost
wrote: On 16/11/2017 05:41, Peter Dimov wrote:
It's all slightly misleading anyway, because for the small matrix case the copy/move constructors don't actually have side effects and therefore get optimized out; they are only relevant in the case of something like std::string where copy/move aren't defaulted.
That was my point; your test code doesn't test copy elision because your constructors have side effects, so can't be elided.
Copy elision (RVO/NRVO) is part of the standard only because it allows the compiler to elide a copy-ctor even if it has side-effects. In other words, if the compiler would apply copy elision, you would see the side-effects to change.
Le 13/11/2017 à 15:20, Beman Dawes via Boost a écrit :
Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
For example
struct Me : make_operators_for<Me>{ Me& operator+=(Me const&); // You get + bool operator<(Me const&) const; // You get all relops (<=> will make that obsolete) Me& operator++(); // you get postfix //etc. };
Today we have the facilities and compilers to make that happen.
What do you think? Who should I ask?
Anyone doing any work on operators or have any thoughts about updating boost::operators?
I find all or nothing approach innovative. But not all the innovations are good. I've always preferred to master the proposed interface? so I prefer to request for set of operators explicitly, e.g. I could ask for arithmetic_operators. Note that the default don't map what we could always expect. E.g. when we are defining a counter, not all the operations supported by an number are desired (See e.g. chrono::duration, where we want to add durations but not to multiply them. I've been working since a long time on strong types wrapper that could inherit some of the operations of the underlying type,but not all. The design of the valid operations is not easy. See [1] for some examples requiring a restricted number of operations. While the scope of the PO subject is different (defining operations from other operation on the same type), I believe that it is worth considering some strong types as concrete examples, and I don't believe there is a single case where we want all the operations. So before going with the all or nothing approach I would like to see some concrete examples where this will be useful. Best, Vicente [1] Strong types POC https://github.com/viboes/std-make/tree/master/include/experimental/fundamen... https://github.com/viboes/std-make/tree/master/example/strong https://github.com/viboes/std-make/tree/master/test/strong
On Mon, Nov 13, 2017 at 9:20 AM, Beman Dawes via Boost
Peter Sommerlad, committee member and C++Now presenter who often proposes additions to fill in holes in the standard library, asked me:
Are you aware of anybody who tried to provide a boost::operators style of automatically providing additional operators with a single base class that through SFINAE injects all possible operators based on the ones defined in the template parameter? This won't give you the control of the current boost::operators, but would be much easier to teach.
For example
struct Me : make_operators_for<Me>{ Me& operator+=(Me const&); // You get + bool operator<(Me const&) const; // You get all relops (<=> will make that obsolete) Me& operator++(); // you get postfix //etc. };
Today we have the facilities and compilers to make that happen.
What do you think? Who should I ask?
Anyone doing any work on operators or have any thoughts about updating boost::operators?
At one point I made a CRTP base that conditionally provides operators,
but only for comparisons (==, !=, <, >, <=, >=). In order to not
negatively impact compilation and to preserve the operators as inline,
non-template friends, I had to make some different design decisions
from Boost.Operators.
First, in order to avoid subtleties, when you inherit from the CRTP
base you specify "dependencies" in addition to the ChildType. These
dependencies, in practice, are a list of all of the other bases and
members of the ChildType that might contribute to equivalence and
ordering. The operators are then conditionally provided by the CRTP
base if and only if any required operators are callable for each
dependency (i.e. == and != are only generated if the following is
valid for each dependency (std::declval
participants (14)
-
Beman Dawes
-
Daniel Frey
-
David Stone
-
Edward Diener
-
Gavin Lambert
-
Gevorg Voskanyan
-
Hans Dembinski
-
Matt Calabrese
-
Niall Douglas
-
Peter Dimov
-
Richard Hodges
-
Robert Ramey
-
Seth
-
Vicente J. Botet Escriba