[operators] The future of Boost.Operators
Hello, for a long time there hasn't been any significant update of Boost.Operators and I had an easy job being its maintainer for the past 10+ years. I guess that will change soon. :) I would like to discuss the future of Boost.Operators, as several issues and options have piled up in the past. Here are some points to take into account: 1) Support for rvalue references to generate fewer temporaries. (where available) 2) Support for noexcept. (where available) 3) Support for constexpr if this is applicable at all. I haven't found the time to properly research it yet. 4) Allow dedicated commutative and non-commutative versions for several operators, leading to fewer temporaries for commutative versions in several cases. 5) Remove work-arounds for ancient compilers, general cleanup. I worked on a clean rewrite (called df.operators) which supports 1), 2) and 4) (and obviously 5)) and published it a few weeks ago on GitHub, see http://github.org/d-frey/operators/. It is meant to show one option, but it is not a suitable replacement for Boost.Operators in its current form. If it turns out to be useful as a test-bed for the next version of Boost.Operators, I will be glad to improve it into this direction but the future of Boost.Operators does not depend on it. That said, I'd like to invite anyone interested to have a look. If applicable to the future of Boost.Operators, please provide feedback on this mailing list. If feedback only concerns df.operators but not Boost, please don't spam the list and write to my private email, thanks! Some issues which need to be discussed/solved for Boost.Operators' future: 1.1) Which implementation should be used for maximum performance/compatibility? 1.1.1) Pass-By-Value as suggested by Dave Abrahams' article. (http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/) 1.1.2) Overloads for Pass-By-Const-LValue-Reference and Pass-By-RValue-Reference. (as in df.operators) See below for why I decided to create 1.1.2) as an alternative to 1.1.1). 4.1) Decide for a default. 4.1.1) Commutative by default (as the current version of Boost.Operators assumes), non-commutative if explicitly requested by the user. 4.1.2) Non-commutative per default, commutative versions need to be explicitly requested by the user. (as in df.operators) 5.1) The main issue is probably the base-class-chaining. 5.1.1) Keep base-class-chaining, makes VC++ happy. (apply EBO and keep the object size small) 5.1.2) Drop base-class-chaining, stop punishing the rest of the world for VC++'s inability to apply the EBO for multiple inheritance. (I might be a bit biased here…) 5.1.3) Any other option I fail to see? (If a MS-employee would like to provide some information on if/when the EBO-problem will be fixed (here or in private email), I'd be grateful. If it is already fixed, I apologize, but the last information I got about VS2012 suggests EBO is not applied for multiple inheritance.) I think this should be enough to get some discussion going… Best regards, Daniel Why 1.1.2) as an alternative to 1.1.1)? Consider the following test program: #include <iostream> #include <utility> struct A { A() { std::cout << "A::A()" << std::endl; } A( const A& ) { std::cout << "A::A(const A&)" << std::endl; } A( A&& ) { std::cout << "A::A(A&&)" << std::endl; } ~A() { std::cout << "A::~A()" << std::endl; } A& operator+=( const A& ) { std::cout << "+=" << std::endl; return *this; } }; // #define BY_VALUE #ifdef BY_VALUE A operator+( A lhs, const A& rhs ) { lhs += rhs; return lhs; } #else A operator+( const A& lhs, const A& rhs ) { A nrv( lhs ); nrv += rhs; return nrv; } A&& operator+( A&& lhs, const A& rhs ) { lhs += rhs; return std::move( lhs ); } #endif int main() { A a, b, c, d; A r = a + b + c + d; } The above code, compiled with G++ or Clang prints: A::A() A::A() A::A() A::A() A::A(const A&) += += += A::A(A&&) A::~A() A::~A() A::~A() A::~A() A::~A() A::~A() If you define BY_VALUE, it prints: A::A() A::A() A::A() A::A() A::A(const A&) += A::A(A&&) += A::A(A&&) += A::A(A&&) A::~A() A::~A() A::~A() A::~A() A::~A() A::~A() A::~A() A::~A()
Hello, On Mon, 22 Apr 2013, Daniel Frey wrote:
I would like to discuss the future of Boost.Operators, as several issues and options have piled up in the past. Here are some points to take into account:
1) Support for rvalue references to generate fewer temporaries. (where available)
I notice that some of your operators return rvalue references. It seems that everybody tries that, and eventually goes back to returning plain rvalues (see the discussion about boost.multiprecision for instance).
3) Support for constexpr if this is applicable at all. I haven't found the time to properly research it yet.
Seems easiest to wait for C++14, where you want constexpr in front of every function.
(If a MS-employee would like to provide some information on if/when the EBO-problem will be fixed (here or in private email), I'd be grateful.
It seems that MS decided to break the library ABI at every release and the core ABI almost never, and the change you are asking for would break core ABI. -- Marc Glisse
On 22.04.2013, at 22:10, Marc Glisse
On Mon, 22 Apr 2013, Daniel Frey wrote:
I would like to discuss the future of Boost.Operators, as several issues and options have piled up in the past. Here are some points to take into account:
1) Support for rvalue references to generate fewer temporaries. (where available)
I notice that some of your operators return rvalue references. It seems that everybody tries that, and eventually goes back to returning plain rvalues (see the discussion about boost.multiprecision for instance).
The only "issue" that I am aware of is that you can no longer extend the returned temporary's lifetime by binding it to an lvalue reference. My problem with this argument is that I don't buy it. If someone writes: const A& r = a + b + c; // (1) instead of const A r = a + b + c; // (2) then I would really say it's the user's fault. Why? Because you always need to check if operator+ returns a value and not a reference. Simply assuming it does without checking the documentation (not the implementation!) is a bug. If a library/class/... does not guarantee that a function (or operator) returns a value, one should not rely on being able to use (1) and always use (2) instead. Or are there any other issues that I fail to see? Regards, Daniel
The only issues I can think of are going to be results of misuse and shouldn't be intended operation. However, the main concern is that in practical code it may be difficult to identify and isolate issues which could crop up. The question is do we try to provide safer code, more efficient code, or both? I had some questions about general cleanup planned. What compiler-specific code should the re-write reasonably have to take into account? Only standard-compliant features, or are there some notable exceptions of known issues? I'm assuming we're going to at a minimum support C++03 and C++11 versions. Also, should we worry about maintaining the NRVO code? The last thing I can think of is what about the left operators? Should they be provided in a similar fashion like commutative/non commutative?
On 23.04.2013, at 08:48, Andrew Ho
However, the main concern is that in practical code it may be difficult to identify and isolate issues which could crop up. The question is do we try to provide safer code, more efficient code, or both?
If possible: Obviously both. I think that efficiency should be the primary goal, otherwise people will end up not using Boost.Operators in performance-critical applications and they will write their own code which might have even more bugs/problems. Safety is therefore also important, but in case of doubt I think we should use the more efficient code and document the pitfalls. I guess C++ already has a history of that approach :) If the only problem is binding a reference to the result it's also explicitly visible in the user's code, it's not something that introduces a bug silently.
What compiler-specific code should the re-write reasonably have to take into account? Only standard-compliant features, or are there some notable exceptions of known issues? I'm assuming we're going to at a minimum support C++03 and C++11 versions. Also, should we worry about maintaining the NRVO code?
The major guidelines from my point of view are: - If no one can and will test an existing work-around, there is no point in keeping it. - If someone is able and willing to test, the work-arounds should not have any negative impact for conforming/good compilers. - C++11 should be optional, using Boost.Config to detect support for rvalue references, noexcept, … - NRVO: Do you mean the work-around in case the compiler is *not* able to apply the NRVO? Good question.
The last thing I can think of is what about the left operators? Should they be provided in a similar fashion like commutative/non commutative?
I don't quite understand what you have in mind, could you elaborate, please? For df.operators, you already have the choice of using commutative_foo
I don't quite understand what you have in mind, could you elaborate,
have the choice of using commutative_foo
which provides both T foo U and U foo T (as it is commutative) and for the non-commutative version, you still have foo< T, U > for T foo U and foo_left< T, U > for U foo T. In general, I see two major options to improve Boost.Operators:
1) Small steps to improve the existing implementation, be as compatible as
about changes. That might also mean to end up with a compromise and not with the best API and implementation for the conforming and good compilers and the people using them.
2) Leave Boost.Operators as it is and develop Boost.Operators v2 (using df.operators as a base?). This should allow for breaking the API for the better and to have a much cleaner implementation. Old compilers that need strange work-arounds can still use Boost.Operators v1 and they are unlikely to benefit from rvalue-reference support anyways.
I think that 2) might be the better option, giving us more freedom, reduce
decisions and the implementation while not breaking any existing code: Users need to explicitly decide to switch to v2, otherwise all existing code remains untouched. Everyone,
Daniel Frey
preferences for either 1) or 2), thanks!
Ahh, true. left is only relevant for non-commutative.
What about using partial template specialization for handling
commutative/non-commutative versions? This should make implementing 1) much
easier, and I think is a slightly more elegant/flexible solution.
Here's some sample code:
// non-commutative
template
On Tue, Apr 23, 2013 at 10:45 AM, Andrew Ho
Daniel Frey
writes: I don't quite understand what you have in mind, could you elaborate, please? For df.operators, you already have the choice of using commutative_foo
which provides both T foo U and U foo T (as it is commutative) and for the non-commutative version, you still have foo< T, U > for T foo U and foo_left< T, U > for U foo T. In general, I see two major options to improve Boost.Operators:
1) Small steps to improve the existing implementation, be as compatible as possible, be very conservative about changes. That might also mean to end up with a compromise and not with the best API and implementation for the conforming and good compilers and the people using them.
2) Leave Boost.Operators as it is and develop Boost.Operators v2 (using df.operators as a base?). This should allow for breaking the API for the better and to have a much cleaner implementation. Old compilers that need strange work-arounds can still use Boost.Operators v1 and they are unlikely to benefit from rvalue-reference support anyways.
I think that 2) might be the better option, giving us more freedom, reduce the complexity of discussions, decisions and the implementation while not breaking any existing code: Users need to explicitly decide to switch to v2, otherwise all existing code remains untouched. Everyone, please give me your preferences for either 1) or 2), thanks!
Offhand and without thinking about it too much, I like 2.
Ahh, true. left is only relevant for non-commutative.
What about using partial template specialization for handling commutative/non-commutative versions? This should make implementing 1) much easier, and I think is a slightly more elegant/flexible solution.
Here's some sample code:
// non-commutative template
Ideally this goes without saying, but at the very least this template parameter should be an enum, rather than a bool.
struct addable1 { friend T operator +( const T& lhs, const T& rhs ) { return std::move(T(lhs) += rhs); } friend T&& operator +( T&& lhs, const T& rhs ) { return std::move(lhs += rhs); } friend T&& operator +( T&& lhs, T&& rhs ) { return std::move(lhs += std::move(rhs)); } };
// commutative template <class T> struct addable1
{ friend T operator +( const T& lhs, const T& rhs ) { return std::move(T(lhs) += rhs); } friend T&& operator +( T&& lhs, const T& rhs ) { return std::move(lhs += rhs); } friend T&& operator +( T&& lhs, T&& rhs ) { return std::move(lhs += std::move(rhs)); } friend T&& operator +( const T& lhs, T&& rhs ) { return std::move(rhs += lhs); } };
- Jeff
On 23.04.2013, at 19:45, Andrew Ho
What about using partial template specialization for handling commutative/non-commutative versions? This should make implementing 1) much easier, and I think is a slightly more elegant/flexible solution.
I don't see how it would make the implementation any easier, you still need to commutative and the non-commutative version and they can't really re-use each other's code. It also means you end up with a terrible interface, consider addable1< T > addable1< T, true > // true what? that doesn't speak for itself vs. commutative_addable< T > // ah, this is a commutative operator! addable< T > // does not have commutative in the name -> doesn't require/exploit it! The safe, although slightly less efficient default Note that I'd like to get rid of the addable1/addable2 distinction as well, just addable< T > or addable< T, U > is IMHO easier. Daniel
I don't see how it would make the implementation any easier, you still need to commutative and the non-commutative version and they can't really re-use each other's code. It also means you end up with a terrible interface, consider
addable1< T > addable1< T, true > // true what? that doesn't speak for itself
vs.
commutative_addable< T > // ah, this is a commutative operator! addable< T > // does not have commutative in the name -> doesn't require/exploit it! The safe, although slightly less efficient default
Fair enough, though using an enum would be able to capture commutative,
non_commutative_right, and non_commutative_left.
addable1<T> // default to non_commutative_right or some behavior which works
best for the particular operator
addable1
Note that I'd like to get rid of the addable1/addable2 distinction as well, just addable< T > or addable< T, U > is IMHO easier.
I like this idea, though I think this would mean that the current system of
using single chain inheritance unwieldy. To specify a specific chain, the
user would have to extend addable
On 23.04.2013, at 21:56, Andrew Ho
Fair enough, though using an enum would be able to capture commutative, non_commutative_right, and non_commutative_left.
addable1<T> // default to non_commutative_right or some behavior which works best for the particular operator addable1
addable1 addable1
That wouldn't work so easily, as left/right only makes sense for the two-argument version. You'd need something like addable1< T, non_commutative > addable1< T, commutative > addable2< T, U, non_commutative_left > addable2< T, U, non_commutative_right > addable2< T, U, commutative > and you need specializations with static_asserts for invalid cases like addable2< T, U, non_commutative > // no _left or _right -> invalid
Note that I'd like to get rid of the addable1/addable2 distinction as well, just addable< T > or addable< T, U > is IMHO easier.
I like this idea, though I think this would mean that the current system of using single chain inheritance unwieldy. To specify a specific chain, the user would have to extend addable
rather than extending addable . Likewise similar specifications must be made for any other template tracked parameters (addable ). Using different structs all-together (addable/addable_left/addable_commutative) and multiple inheritance would avoid this.
Granted, chaining is quite invasive here. Which is why I'd like to avoid it, see the next paragraph:
There was a note in the current docs about significant code bloat involving multiple inheritance with multiple empty classes, though I haven't been able to reproduce their claims in vc++ 2012. IMO the multiple inheritance route makes more sense conceptually, but I wouldn't want to have object sizes grow unnecessarily. There weren't any notes as to what specific compilers exhibited this behavior, but the comment was made circa ~2000 so perhaps this has been addressed by now. Is this still a significant concern now?
I wish MS would have fixed it. AFAIK the only compiler/ABI that has this problem is VC++/Windows and I already have one user reporting that the problem still exists. If you say you can't reproduce it, I'd like to find out why! What have you tried and which settings do you use? (Note I don't have access to VC++ or Windows, so I need some help). Daniel
Did some more testing, I was able to reproduce the object bloat size with
VS2012, I suspect my previous tests were likely too simple so they got
optimized away, but in the general case (especially with what is used by
boost operators) this doesn't happen. Out of curiosity, does anyone know if
this has been suggested to MS as a bug/enhancement already?
On Tue, Apr 23, 2013 at 3:15 PM, Daniel Frey
On 23.04.2013, at 21:56, Andrew Ho
wrote: Fair enough, though using an enum would be able to capture commutative, non_commutative_right, and non_commutative_left.
addable1<T> // default to non_commutative_right or some behavior which works best for the particular operator addable1
addable1 addable1 That wouldn't work so easily, as left/right only makes sense for the two-argument version. You'd need something like
addable1< T, non_commutative > addable1< T, commutative > addable2< T, U, non_commutative_left > addable2< T, U, non_commutative_right > addable2< T, U, commutative >
and you need specializations with static_asserts for invalid cases like
addable2< T, U, non_commutative > // no _left or _right -> invalid
Note that I'd like to get rid of the addable1/addable2 distinction as well, just addable< T > or addable< T, U > is IMHO easier.
I like this idea, though I think this would mean that the current system of using single chain inheritance unwieldy. To specify a specific chain, the user would have to extend addable
rather than extending addable . Likewise similar specifications must be made for any other template tracked parameters (addable ). Using different structs all-together (addable/addable_left/addable_commutative) and multiple inheritance would avoid this. Granted, chaining is quite invasive here. Which is why I'd like to avoid it, see the next paragraph:
There was a note in the current docs about significant code bloat involving multiple inheritance with multiple empty classes, though I haven't been able to reproduce their claims in vc++ 2012. IMO the multiple inheritance route makes more sense conceptually, but I wouldn't want to have object sizes grow unnecessarily. There weren't any notes as to what specific compilers exhibited this behavior, but the comment was made circa ~2000 so perhaps this has been addressed by now. Is this still a significant concern now?
I wish MS would have fixed it. AFAIK the only compiler/ABI that has this problem is VC++/Windows and I already have one user reporting that the problem still exists. If you say you can't reproduce it, I'd like to find out why! What have you tried and which settings do you use? (Note I don't have access to VC++ or Windows, so I need some help).
Daniel
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
Here's a thought about the direct use of r-value references vs. pass by value argument: We are planning on providing a version which is supposed to support compilers without move semantics. However, these implementations are close to, perhaps even exactly identical to the code that would take advantage of move semantics via pass by value. Say we allowed a user-accessible define which would allow the programmer to decide? something like: // BOOST_HAS_RVALUE_REFS is a feature availability check // BOOST_UNSAFE_MOVE is user-definable override to use faster, unsafe version #if defined(BOOST_HAS_RVALUE_REFS) && defined(BOOST_UNSAFE_MOVE) // use 4 overload version #else // use pass-by-value version #endif If the user is more concerned with safety over micro-optimizations, the default behavior would resort to pass by value (good for backwards compatibility). However, the user would have the option available to easily use the faster version if they deem it necessary.
On Tue, Apr 23, 2013 at 11:06 PM, Andrew Ho
Here's a thought about the direct use of r-value references vs. pass by value argument:
We are planning on providing a version which is supposed to support compilers without move semantics. However, these implementations are close to, perhaps even exactly identical to the code that would take advantage of move semantics via pass by value.
Say we allowed a user-accessible define which would allow the programmer to decide?
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that. something like:
// BOOST_HAS_RVALUE_REFS is a feature availability check // BOOST_UNSAFE_MOVE is user-definable override to use faster, unsafe version #if defined(BOOST_HAS_RVALUE_REFS) && defined(BOOST_UNSAFE_MOVE) // use 4 overload version #else // use pass-by-value version #endif
If the user is more concerned with safety over micro-optimizations, the default behavior would resort to pass by value (good for backwards compatibility).
Remind me how the 4-overload-version is unsafe, again? However, the user would have the option available to easily use the faster
version if they deem it necessary.
- Jeff
Remind me how the 4-overload-version is unsafe, again?
There's the potential for a user to assign a reference variable to an invalid temporary: T &var = a + b + c; I am of the opinion this is a usage error (as is Daniel), but none-the-less, was previously valid because the operators returned by value. Just as large a problem is that if this error does occur in real code (old or new), it can be difficult to identify.
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
We will need at least the compiler feature check switch since rvalue references will cause compilers which don't support the feature fail. After a little digging, the commutative operators will always need special handling since they directly use rvalue refs. On Wed, Apr 24, 2013 at 11:04 AM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Tue, Apr 23, 2013 at 11:06 PM, Andrew Ho
wrote: Here's a thought about the direct use of r-value references vs. pass by value argument:
We are planning on providing a version which is supposed to support compilers without move semantics. However, these implementations are close to, perhaps even exactly identical to the code that would take advantage of move semantics via pass by value.
Say we allowed a user-accessible define which would allow the programmer to decide?
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
something like:
// BOOST_HAS_RVALUE_REFS is a feature availability check // BOOST_UNSAFE_MOVE is user-definable override to use faster, unsafe version #if defined(BOOST_HAS_RVALUE_REFS) && defined(BOOST_UNSAFE_MOVE) // use 4 overload version #else // use pass-by-value version #endif
If the user is more concerned with safety over micro-optimizations, the default behavior would resort to pass by value (good for backwards compatibility).
Remind me how the 4-overload-version is unsafe, again?
However, the user would have the option available to easily use the faster
version if they deem it necessary.
- Jeff
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
On Wed, Apr 24, 2013 at 1:36 PM, Andrew Ho
Remind me how the 4-overload-version is unsafe, again?
There's the potential for a user to assign a reference variable to an invalid temporary:
T &var = a + b + c;
Does operator+ return by value or by reference? If it returns by value, how is the above possible? Is T a typedef or template parameter for a const-qualified type in your example above? I am of the opinion this is a usage error (as is Daniel), but
none-the-less, was previously valid because the operators returned by value.
Okay, now I'm really confused. I would think return-by-reference would allow the above, but return-by-value would not. And how is this related to whether operator+'s parameters are by-value (1 overload) or by-reference (4 overloads)? I mean, other than the former precluding return-by-reference, of course. Just as large a problem is that if this error does occur in real
code (old or new), it can be difficult to identify.
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
We will need at least the compiler feature check switch since rvalue references will cause compilers which don't support the feature fail.
Yeah, that's fine; you're switching on compiler features, and you're switching to an objectively better implementation. Having a macro that switches *all* uses of Boost.Operators to either "safe" or "fast" (whatever those mean) implies that you can't mix safe uses with fast uses. After
a little digging, the commutative operators will always need special handling since they directly use rvalue refs.
On Wed, Apr 24, 2013 at 11:04 AM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Tue, Apr 23, 2013 at 11:06 PM, Andrew Ho
wrote: Here's a thought about the direct use of r-value references vs. pass by value argument:
We are planning on providing a version which is supposed to support compilers without move semantics. However, these implementations are close to, perhaps even exactly identical to the code that would take advantage of move semantics via pass by value.
Say we allowed a user-accessible define which would allow the programmer to decide?
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
something like:
// BOOST_HAS_RVALUE_REFS is a feature availability check // BOOST_UNSAFE_MOVE is user-definable override to use faster, unsafe version #if defined(BOOST_HAS_RVALUE_REFS) && defined(BOOST_UNSAFE_MOVE) // use 4 overload version #else // use pass-by-value version #endif
If the user is more concerned with safety over micro-optimizations, the default behavior would resort to pass by value (good for backwards compatibility).
Remind me how the 4-overload-version is unsafe, again?
However, the user would have the option available to easily use the faster
version if they deem it necessary.
- Jeff
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
I was assuming T was a given type: struct T : addable<T> { // ... } But I did some testing, and looks like the above code which normally should fail if operator+ returned a lvalue reference actually works with rvalues: struct T : boost::addable<T> { T(void) { std::cout << "T(): " << this << std::endl; } T(const T &val) { std::cout << "T(const T&): " << this << std::endl; } T(T &&val) { std::cout << "T(T&&): " << this << std::endl; } ~T(void) { std::cout << "~T(): " << this << std::endl; } T& operator +=(const T& op) { std::cout << this << " += " << &op << std::endl; return *this; } T& operator =(const T& op) { std::cout << this << " = const & " << &op << std::endl; return *this; } T& operator =(T&& op) { std::cout << this << " = && " << &op << std::endl; return *this; } }; Output using VS2012 (displayed output in debug mode, but similar results occur in release mode): T &result = t1 + t2 + t3; T(): 0040FEDB T(): 0040FECF T(): 0040FEC3 T(const T&): 0040FCD7 0040FCD7 += 0040FECF T(T&&): 0040FEAB ~T(): 0040FCD7 0040FEAB += 0040FEC3 &result = 0040FEAB T result = t1 + t2 + t3; T(): 0035F9FF T(): 0035F9F3 T(): 0035F9E7 T(const T&): 0035F7FB 0035F7FB += 0035F9F3 T(T&&): 0035F90F ~T(): 0035F7FB 0035F90F += 0035F9E7 T(T&&): 0035F9DB ~T(): 0035F90F &result = 0035F9DB Is this just MS trying to be "clever", or are these results standard-compliant (and equally important, viable on all other rvalue-ref aware compilers)? If this is indeed expected behavior, then there shouldn't be any fuss over unsafe behavior at all, and the 4-overload version can be used without any problems (assuming we didn't miss any corner cases). On Wed, Apr 24, 2013 at 3:41 PM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Wed, Apr 24, 2013 at 1:36 PM, Andrew Ho
wrote: Remind me how the 4-overload-version is unsafe, again?
There's the potential for a user to assign a reference variable to an invalid temporary:
T &var = a + b + c;
Does operator+ return by value or by reference? If it returns by value, how is the above possible? Is T a typedef or template parameter for a const-qualified type in your example above?
I am of the opinion this is a usage error (as is Daniel), but
none-the-less, was previously valid because the operators returned by value.
Okay, now I'm really confused. I would think return-by-reference would allow the above, but return-by-value would not.
And how is this related to whether operator+'s parameters are by-value (1 overload) or by-reference (4 overloads)? I mean, other than the former precluding return-by-reference, of course.
Just as large a problem is that if this error does occur in real
code (old or new), it can be difficult to identify.
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
We will need at least the compiler feature check switch since rvalue references will cause compilers which don't support the feature fail.
Yeah, that's fine; you're switching on compiler features, and you're switching to an objectively better implementation.
Having a macro that switches *all* uses of Boost.Operators to either "safe" or "fast" (whatever those mean) implies that you can't mix safe uses with fast uses.
a little digging, the commutative operators will always need special handling since they directly use rvalue refs.
On Wed, Apr 24, 2013 at 11:04 AM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Tue, Apr 23, 2013 at 11:06 PM, Andrew Ho
wrote: Here's a thought about the direct use of r-value references vs. pass by value argument:
We are planning on providing a version which is supposed to support compilers without move semantics. However, these implementations are close to, perhaps even exactly identical to the code that would take advantage of move semantics via pass by value.
Say we allowed a user-accessible define which would allow the programmer to decide?
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
something like:
// BOOST_HAS_RVALUE_REFS is a feature availability check // BOOST_UNSAFE_MOVE is user-definable override to use faster, unsafe version #if defined(BOOST_HAS_RVALUE_REFS) && defined(BOOST_UNSAFE_MOVE) // use 4 overload version #else // use pass-by-value version #endif
If the user is more concerned with safety over micro-optimizations,
After the
default behavior would resort to pass by value (good for backwards compatibility).
Remind me how the 4-overload-version is unsafe, again?
However, the user would have the option available to easily use the faster
version if they deem it necessary.
- Jeff
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
On Wed, Apr 24, 2013 at 5:31 PM, Andrew Ho
I was assuming T was a given type:
struct T : addable<T> { // ... }
But I did some testing, and looks like the above code which normally should fail if operator+ returned a lvalue reference actually works with rvalues:
struct T : boost::addable<T> { T(void) { std::cout << "T(): " << this << std::endl; }
T(const T &val) { std::cout << "T(const T&): " << this << std::endl; }
T(T &&val) { std::cout << "T(T&&): " << this << std::endl; }
~T(void) { std::cout << "~T(): " << this << std::endl; }
T& operator +=(const T& op) { std::cout << this << " += " << &op << std::endl; return *this; } T& operator =(const T& op) { std::cout << this << " = const & " << &op << std::endl; return *this; }
T& operator =(T&& op) { std::cout << this << " = && " << &op << std::endl; return *this; } };
Output using VS2012 (displayed output in debug mode, but similar results occur in release mode):
T &result = t1 + t2 + t3;
T(): 0040FEDB T(): 0040FECF T(): 0040FEC3 T(const T&): 0040FCD7 0040FCD7 += 0040FECF T(T&&): 0040FEAB ~T(): 0040FCD7 0040FEAB += 0040FEC3
&result = 0040FEAB
T result = t1 + t2 + t3;
T(): 0035F9FF T(): 0035F9F3 T(): 0035F9E7 T(const T&): 0035F7FB 0035F7FB += 0035F9F3 T(T&&): 0035F90F ~T(): 0035F7FB 0035F90F += 0035F9E7 T(T&&): 0035F9DB ~T(): 0035F90F
&result = 0035F9DB
Is this just MS trying to be "clever", or are these results standard-compliant (and equally important, viable on all other rvalue-ref aware compilers)? If this is indeed expected behavior, then there shouldn't be any fuss over unsafe behavior at all, and the 4-overload version can be used without any problems (assuming we didn't miss any corner cases).
1) Please don't top-post. 2) I think my responses below still apply. 3) I suspect if you did T& x = f() + g(), where f() and g() are rvalues, you'd be in trouble. On Wed, Apr 24, 2013 at 3:41 PM, Jeffrey Lee Hellrung, Jr. <
jeffrey.hellrung@gmail.com> wrote:
On Wed, Apr 24, 2013 at 1:36 PM, Andrew Ho
wrote: Remind me how the 4-overload-version is unsafe, again?
There's the potential for a user to assign a reference variable to an invalid temporary:
T &var = a + b + c;
Does operator+ return by value or by reference? If it returns by value, how is the above possible? Is T a typedef or template parameter for a const-qualified type in your example above?
I am of the opinion this is a usage error (as is Daniel), but
none-the-less, was previously valid because the operators returned by value.
Okay, now I'm really confused. I would think return-by-reference would allow the above, but return-by-value would not.
And how is this related to whether operator+'s parameters are by-value (1 overload) or by-reference (4 overloads)? I mean, other than the former precluding return-by-reference, of course.
Just as large a problem is that if this error does occur in real
code (old or new), it can be difficult to identify.
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
We will need at least the compiler feature check switch since rvalue references will cause compilers which don't support the feature fail.
Yeah, that's fine; you're switching on compiler features, and you're switching to an objectively better implementation.
Having a macro that switches *all* uses of Boost.Operators to either "safe" or "fast" (whatever those mean) implies that you can't mix safe uses with fast uses.
After
a little digging, the commutative operators will always need special handling since they directly use rvalue refs.
On Wed, Apr 24, 2013 at 11:04 AM, Jeffrey Lee Hellrung, Jr. < jeffrey.hellrung@gmail.com> wrote:
On Tue, Apr 23, 2013 at 11:06 PM, Andrew Ho
wrote:
Here's a thought about the direct use of r-value references vs. pass by value argument:
We are planning on providing a version which is supposed to support compilers without move semantics. However, these implementations are close to, perhaps even exactly identical to the code that would take advantage of move semantics via pass by value.
Say we allowed a user-accessible define which would allow the programmer to decide?
A macro which globally switches the effect of all uses of Boost.Operators? Let's avoid that.
something like:
// BOOST_HAS_RVALUE_REFS is a feature availability check // BOOST_UNSAFE_MOVE is user-definable override to use faster,
version #if defined(BOOST_HAS_RVALUE_REFS) && defined(BOOST_UNSAFE_MOVE) // use 4 overload version #else // use pass-by-value version #endif
If the user is more concerned with safety over micro-optimizations,
unsafe the
default behavior would resort to pass by value (good for backwards compatibility).
Remind me how the 4-overload-version is unsafe, again?
However, the user would have the option available to easily use the faster
version if they deem it necessary.
- Jeff
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
-- Andrew Ho
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
1) Please don't top-post.
Oops, my bad. Mail client/user fail.
3) I suspect if you did T& x = f() + g(), where f() and g() are rvalues, you'd be in trouble.
I take it you have: T&& f(); T&& g(): correct? This does indeed fail, but this will fail using either r-value refs or just return by value operator overloading. In fact, this fails for me even if I tried: T x = f() + g(); In all cases, the temporary destructor was being called as a result of f() or g() returning, not a result of operator overloading definition or type of x. Changing the definitions to: T f(); T g(); and both operator overloading implementations (r-value refs or return by value) succeeds. I don't quite understand why the r-value refs operator overloads are able to extend the lifetime of the rvalues even though the function return rvalues don't have their lifetimes extended. Again, I'm not sure if there is some VS2012 specific behavior going on.
2) I think my responses below still apply.
Are you referring to using a user-accessible macro switch, or the problems associated with using the 4-overloads?
On Wed, Apr 24, 2013 at 10:24 PM, Andrew Ho
3) I suspect if you did T& x = f() + g(), where f() and g() are rvalues, you'd be in trouble.
I take it you have:
T&& f(); T&& g():
Rvalues, not rvalue references :) The above obviously would (likely) have dangling reference issues. [...]
Changing the definitions to:
T f(); T g();
and both operator overloading implementations (r-value refs or return by value) succeeds. I don't quite understand why the r-value refs operator overloads are able to extend the lifetime of the rvalues even though the function return rvalues don't have their lifetimes extended.
Again, I'm not sure if there is some VS2012 specific behavior going on.
Maybe we're talking past each other. I have 3 overload set variants in mind: (A) T operator?(T, T); (B) T operator?(T const &, T const &); ... T operator?(T&&, T&&); (C) T operator?(T const &, T const &); ... T&& operator?(T&&, T&&); AFAICT, neither (A) nor (B) have any safety issues. Oh, but now I see the problem with (C): T&&'s are implicitly convertible to T&'s...that's what I was missing before. Sorry for being dense. So if you do T& a = b + c; you'll get a compiler error with (A) or (B) and a runtime error with (C). Well, maybe you can return a wrapped T&& with an implicit conversion operator to T&&: wrapped_rref<T> operator?(T&&, T&&); where template< class T > struct wrapped_rref { operator T&&() const; operator T const &() const; }; Not sure offhand if that would even prevent the T& a = b + c; usage, nor if it would handicap many other common use cases.
2) I think my responses below still apply.
Are you referring to using a user-accessible macro switch, or the problems associated with using the 4-overloads?
The former, for sure. I assume you mean (C) above for the latter. - Jeff
On Thu, 25 Apr 2013, Jeffrey Lee Hellrung, Jr. wrote:
On Wed, Apr 24, 2013 at 10:24 PM, Andrew Ho
wrote: [...] 3) I suspect if you did T& x = f() + g(), where f() and g() are rvalues, you'd be in trouble.
I take it you have:
T&& f(); T&& g():
Rvalues, not rvalue references :) The above obviously would (likely) have dangling reference issues.
[...]
Changing the definitions to:
T f(); T g();
and both operator overloading implementations (r-value refs or return by value) succeeds. I don't quite understand why the r-value refs operator overloads are able to extend the lifetime of the rvalues even though the function return rvalues don't have their lifetimes extended.
Again, I'm not sure if there is some VS2012 specific behavior going on.
Maybe we're talking past each other. I have 3 overload set variants in mind:
(A) T operator?(T, T);
(B) T operator?(T const &, T const &); ... T operator?(T&&, T&&);
(C) T operator?(T const &, T const &); ... T&& operator?(T&&, T&&);
AFAICT, neither (A) nor (B) have any safety issues. Oh, but now I see the problem with (C): T&&'s are implicitly convertible to T&'s...that's what I was missing before. Sorry for being dense.
So if you do
T& a = b + c;
you'll get a compiler error with (A) or (B) and a runtime error with (C).
Er, no. That might be true with Microsoft's compiler and its extra "features", but regular compilers will complain: error: invalid initialization of non-const reference of type 'T&' from an rvalue of type 'T' Now you can ask the same question with T const& instead... -- Marc Glisse
On Thu, Apr 25, 2013 at 10:03 AM, Marc Glisse
On Thu, 25 Apr 2013, Jeffrey Lee Hellrung, Jr. wrote:
On Wed, Apr 24, 2013 at 10:24 PM, Andrew Ho
wrote: [...]
3) I suspect if you did T& x = f() + g(), where f() and g() are rvalues,
you'd be in trouble.
I take it you have:
T&& f(); T&& g():
Rvalues, not rvalue references :) The above obviously would (likely) have dangling reference issues.
[...]
Changing the definitions to:
T f(); T g();
and both operator overloading implementations (r-value refs or return by value) succeeds. I don't quite understand why the r-value refs operator overloads are able to extend the lifetime of the rvalues even though the function return rvalues don't have their lifetimes extended.
Again, I'm not sure if there is some VS2012 specific behavior going on.
Maybe we're talking past each other. I have 3 overload set variants in mind:
(A) T operator?(T, T);
(B) T operator?(T const &, T const &); ... T operator?(T&&, T&&);
(C) T operator?(T const &, T const &); ... T&& operator?(T&&, T&&);
AFAICT, neither (A) nor (B) have any safety issues. Oh, but now I see the problem with (C): T&&'s are implicitly convertible to T&'s...that's what I was missing before. Sorry for being dense.
So if you do
T& a = b + c;
you'll get a compiler error with (A) or (B) and a runtime error with (C).
Er, no. That might be true with Microsoft's compiler and its extra "features", but regular compilers will complain:
error: invalid initialization of non-const reference of type 'T&' from an rvalue of type 'T'
Are you sure we're talking about the same thing? This looks like the kind
of error that (A) or (B) would produce; would not (C) say something like
invalid initialization of non-const reference of type 'T&' from type 'T&&'
???
I swear I just tried a T& y = static_cast
Now you can ask the same question with T const& instead...
Sure.
On Thu, 25 Apr 2013, Jeffrey Lee Hellrung, Jr. wrote:
T& a = b + c;
you'll get a compiler error with (A) or (B) and a runtime error with (C).
Er, no. That might be true with Microsoft's compiler and its extra "features", but regular compilers will complain:
error: invalid initialization of non-const reference of type 'T&' from an rvalue of type 'T'
Are you sure we're talking about the same thing? This looks like the kind of error that (A) or (B) would produce; would not (C) say something like
invalid initialization of non-const reference of type 'T&' from type 'T&&'
???
I swear I just tried a T& y = static_cast
(x) on gcc and finding it compiled fine.
I just tried to compile this program with g++ -std=c++0x -Wall -c test.cc for versions of gcc: 4.4, 4.6, 4.7, and 4.9 and they all rejected it with pretty much the same error message. I don't understand what the difference can be with your tests :-( struct A{}; void f(){ A a; A&r=static_cast(a); } And for the record, clang++'s error message: error: non-const lvalue reference to type 'A' cannot bind to a temporary of type 'A' -- Marc Glisse
On Thu, Apr 25, 2013 at 11:03 AM, Marc Glisse
On Thu, 25 Apr 2013, Jeffrey Lee Hellrung, Jr. wrote:
T& a = b + c;
you'll get a compiler error with (A) or (B) and a runtime error with (C).
Er, no. That might be true with Microsoft's compiler and its extra "features", but regular compilers will complain:
error: invalid initialization of non-const reference of type 'T&' from an rvalue of type 'T'
Are you sure we're talking about the same thing? This looks like the kind of error that (A) or (B) would produce; would not (C) say something like
invalid initialization of non-const reference of type 'T&' from type 'T&&'
???
I swear I just tried a T& y = static_cast
(x) on gcc and finding it compiled fine. I just tried to compile this program with g++ -std=c++0x -Wall -c test.cc for versions of gcc: 4.4, 4.6, 4.7, and 4.9 and they all rejected it with pretty much the same error message. I don't understand what the difference can be with your tests :-(
struct A{}; void f(){ A a; A&r=static_cast(a); }
And for the record, clang++'s error message:
error: non-const lvalue reference to type 'A' cannot bind to a temporary of type 'A'
Yeah I probably tried it too quickly and screwed something up; I get errors now. Thanks for setting me straight! - Jeff
On Tue, Apr 23, 2013 at 12:42 AM, Daniel Frey
On 22.04.2013, at 22:10, Marc Glisse
wrote: On Mon, 22 Apr 2013, Daniel Frey wrote:
I would like to discuss the future of Boost.Operators, as several issues and options have piled up in the past. Here are some points to take into account:
1) Support for rvalue references to generate fewer temporaries. (where available)
I notice that some of your operators return rvalue references. It seems that everybody tries that, and eventually goes back to returning plain rvalues (see the discussion about boost.multiprecision for instance).
The only "issue" that I am aware of is that you can no longer extend the returned temporary's lifetime by binding it to an lvalue reference. My problem with this argument is that I don't buy it. If someone writes:
const A& r = a + b + c; // (1)
instead of
const A r = a + b + c; // (2)
then I would really say it's the user's fault. Why? Because you always need to check if operator+ returns a value and not a reference. Simply assuming it does without checking the documentation (not the implementation!) is a bug. If a library/class/... does not guarantee that a function (or operator) returns a value, one should not rely on being able to use (1) and always use (2) instead.
I disagree. I mean, what is the C++03 case that would make (1) invalid before? The operator returns either rvalue or a reference (which is assumed to be bound to some stable value), and both cases work well with (1). And if (1) is valid in C++03 then why should it become invalid in C++11?
-----Original Message----- From: Daniel Frey Sent: Monday, April 22, 2013 3:43 PM
Hello,
for a long time there hasn't been any significant update of Boost.Operators and I had an easy job being its maintainer for the past 10+ years. I guess that will change soon. :)
I would like to discuss the future of Boost.Operators, as several issues and options have piled up in the past. Here are some points to take into account:
1) Support for rvalue references to generate fewer temporaries. (where available) 2) Support for noexcept. (where available) 3) Support for constexpr if this is applicable at all. I haven't found the time to properly research it yet.
I've discussed this somewhere, but this library and constexpr are incompatible. The dependency between operator?? and operator??= is backwards to what constexpr needs. template < typename T > class my_complex { T d[2]; public: my_complex( r = T{}, I = T{} ) : d{r, i} {} //... }; template < typename T > inline constexpr my_complex<T> operator/( my_complex<T> x, T y ) { return my_complex{x.real() / y, x.imag() / y}; } template < typename T > inline my_complex<T> & operator/=( my_complex<T> &x, T y ) { return x = x / y; } You can compose the regular version of an operator in a constexpr way if you have the right initialization. But the compound-assignment operators are inherently mutating, so they can never be constexpr. Defining the regular versions in terms of the compound-assignment ones bars them from being constexpr. You can reverse the dependencies to save code.
4) Allow dedicated commutative and non-commutative versions for several operators, leading to fewer temporaries for commutative versions in several cases. 5) Remove work-arounds for ancient compilers, general cleanup. [TRUNCATE]
Daryle W.
On 25.04.2013, at 13:19, Daryle Walker
3) Support for constexpr if this is applicable at all. I haven't found the time to properly research it yet.
I've discussed this somewhere, but this library and constexpr are incompatible. The dependency between operator?? and operator??= is backwards to what constexpr needs. [...] You can compose the regular version of an operator in a constexpr way if you have the right initialization. But the compound-assignment operators are inherently mutating, so they can never be constexpr. Defining the regular versions in terms of the compound-assignment ones bars them from being constexpr. You can reverse the dependencies to save code.
Thanks Daryle, that really helped me to get a clearer picture on the topic. I think I only have two questions left: 1) How could "reverse the dependencies" save code? Could you elaborate, please? 2) Given a set of non-constexpr overloads, could the user add even more overloads for constexpr and what is required for this to work? Example: struct X : private df::commutative_addable< X > { X& operator+=( const X& ); }; constexpr X operator+( constexpr X lhs, constexpr X rhs ) {…} is the above possible? Are there problems/conflicts? What are the requirements for the non-constexpr overloads and the signature/requirements for the user-defined constexpr-overload to play together nicely? Best regards, Daniel
On Thu, 25 Apr 2013, Daniel Frey wrote:
On 25.04.2013, at 13:19, Daryle Walker
wrote: 3) Support for constexpr if this is applicable at all. I haven't found the time to properly research it yet.
I've discussed this somewhere, but this library and constexpr are incompatible. The dependency between operator?? and operator??= is backwards to what constexpr needs. [...] You can compose the regular version of an operator in a constexpr way if you have the right initialization. But the compound-assignment operators are inherently mutating, so they can never be constexpr. Defining the regular versions in terms of the compound-assignment ones bars them from being constexpr. You can reverse the dependencies to save code.
Thanks Daryle, that really helped me to get a clearer picture on the topic. I think I only have two questions left:
1) How could "reverse the dependencies" save code? Could you elaborate, please?
You want to write only one of += and +. Omitting + (as boost.operators currently allows) saves about the same as omitting += (easier on constexpr, but possibly slower for some applications).
2) Given a set of non-constexpr overloads, could the user add even more overloads for constexpr and what is required for this to work? Example:
struct X : private df::commutative_addable< X > { X& operator+=( const X& ); };
constexpr X operator+( constexpr X lhs, constexpr X rhs ) {…}
is the above possible? Are there problems/conflicts? What are the requirements for the non-constexpr overloads and the signature/requirements for the user-defined constexpr-overload to play together nicely?
That's not how constexpr works, there is no overloading on whether the arguments are constexpr. In C++14, as far as I can tell, you'll be able to add constexpr to += no problem, so for boost operators that means you can add a macro in front of every function, that you'll define as constexpr when you are ready. But in C++11, constexpr functions are essentially pure. They take constants and return a constant. That means += can't be constexpr, and a + implemented in terms of += can't either. On the other hand, one can implement a constexpr + directly, and implement += (not constexpr) in terms of that +. That seems to be what Daryle is suggesting. In any case, it could be nice to have the helpers in both directions. One addable that implements + from +=, and one addable_reverse that implements += in terms of + (you could try a+=b as a=move(a)+b maybe?). -- Marc Glisse
On 25.04.2013, at 23:04, Marc Glisse
On Thu, 25 Apr 2013, Daniel Frey wrote:
2) Given a set of non-constexpr overloads, could the user add even more overloads for constexpr and what is required for this to work? Example:
struct X : private df::commutative_addable< X > { X& operator+=( const X& ); };
constexpr X operator+( constexpr X lhs, constexpr X rhs ) {…}
is the above possible? Are there problems/conflicts? What are the requirements for the non-constexpr overloads and the signature/requirements for the user-defined constexpr-overload to play together nicely?
That's not how constexpr works, there is no overloading on whether the arguments are constexpr.
Oh, OK. I guess that kills constexpr for Boost.Operators in C++11 :-/ Thanks, Daniel
On 25.04.2013, at 23:04, Marc Glisse
In any case, it could be nice to have the helpers in both directions. One addable that implements + from +=, and one addable_reverse that implements += in terms of + (you could try a+=b as a=move(a)+b maybe?).
Given that I currently have 4 overloads for operator+ that usually need a single operator+= to provide efficient operations, I'd like to see how this could work with the reverse and the use-case. Why would anyone want to implement operator+ and generate operator+= from it? And even without a detailed analysis *this=std::move(*this)+value looks and feels just wrong. Seeing something like this in the companies code-base would probably make me go to the author and ask him to fix it by reversing it. :) Of course it could be my lack of imagination and I'd be happy to see an example where it is the obvious/right approach. BR, Daniel
On Thu, 25 Apr 2013, Daniel Frey wrote:
On 25.04.2013, at 23:04, Marc Glisse
wrote: In any case, it could be nice to have the helpers in both directions. One addable that implements + from +=, and one addable_reverse that implements += in terms of + (you could try a+=b as a=move(a)+b maybe?).
Given that I currently have 4 overloads for operator+ that usually need a single operator+= to provide efficient operations, I'd like to see how this could work with the reverse and the use-case. Why would anyone want to implement operator+ and generate operator+= from it? And even without a detailed analysis *this=std::move(*this)+value looks and feels just wrong. Seeing something like this in the companies code-base would probably make me go to the author and ask him to fix it by reversing it. :) Of course it could be my lack of imagination and I'd be happy to see an example where it is the obvious/right approach.
In a number of cases, operations can't work in place (need a larger buffer, object is reference counted, etc). Copying one argument to apply an in-place operation to it is a waste of time, you are going to create a new object for the result anyway. Even in the ref-counted case, the copying may be hard for the compiler to optimize away if the counter is atomic. And if you care more about constexpr than performance, implementing + yourself is a must, but you may still want to avoid the one-liner to implement the corresponding += (ok, that sounds a bit weak). I guess I am mostly thinking of cases where only one of the + overloads matters, the one with const&, const&. Maybe more relevant would be a way for the user to write himself += and +(const&, const&) and let boost add only the other 3 overloads based on +=? -- Marc Glisse
On Thu, Apr 25, 2013 at 9:44 PM, Marc Glisse
On Thu, 25 Apr 2013, Daniel Frey wrote:
On 25.04.2013, at 23:04, Marc Glisse
wrote: [...]
Given that I currently have 4 overloads for operator+ that usually need a
single operator+= to provide efficient operations, I'd like to see how this could work with the reverse and the use-case. Why would anyone want to implement operator+ and generate operator+= from it? And even without a detailed analysis *this=std::move(*this)+value looks and feels just wrong. Seeing something like this in the companies code-base would probably make me go to the author and ask him to fix it by reversing it. :) Of course it could be my lack of imagination and I'd be happy to see an example where it is the obvious/right approach.
In a number of cases, operations can't work in place (need a larger buffer, object is reference counted, etc). Copying one argument to apply an in-place operation to it is a waste of time, you are going to create a new object for the result anyway. Even in the ref-counted case, the copying may be hard for the compiler to optimize away if the counter is atomic. And if you care more about constexpr than performance, implementing + yourself is a must, but you may still want to avoid the one-liner to implement the corresponding += (ok, that sounds a bit weak). I guess I am mostly thinking of cases where only one of the + overloads matters, the one with const&, const&.
Maybe more relevant would be a way for the user to write himself += and +(const&, const&) and let boost add only the other 3 overloads based on +=?
That's an interesting idea. - Jeff
participants (7)
-
Andrew Ho
-
Andrey Semashev
-
Daniel Frey
-
Daryle Walker
-
helloworld922
-
Jeffrey Lee Hellrung, Jr.
-
Marc Glisse