Note: as noted by sellibitze I am not up-to-date on rvalues references, therefore the methods I propose contain mistakes, read his anwser to understand which.
I was reading one of Linus’ rant yesterday and there is (somewhere) a rant against operator overloading.
The complaint it seems is that if you have an object of type S then:
S a = b + c + d + e;
may involve a lot of temporaries.
In C++03, we have copy elision to prevent this:
S a = ((b + c) + d) + e;
I would hope that the last ... + e is optimized, but I wonder how many temporaries are created with user defined operator+.
Someone in the thread suggested the use of Expression Templates to deal with the issue.
Now, this thread dates back to 2007, but nowadays when we think elimination of temporaries, we think Move.
So I was thinking about the set of overload operators we should write not to eliminate temporaries, but to limit the cost of their construction (stealing resources).
S&& operator+(S&& lhs, S const& rhs) { return lhs += rhs; }
S&& operator+(S const& lhs, S&& rhs) { return rhs += lhs; } // *
S&& operator+(S&& lhs, S&& rhs) { return lhs += rhs; }
Does this set of operator seems sufficient ? Is this generalizable (in your opinion) ?
*: this implementation supposes commutativity, it doesn’t work for the infamous string.
If you’re thinking about a custom, move-enabled string class, the proper way to exploit every combination of argument value categories is:
The functions return a prvalue instead of an xvalue. Returning xvalues is usually a very dangerous thing – std::move and std::forward are the obvious exceptions. If you were to return an rvalue reference you’d break code like:
This loop behaves (according to 6.5.4/1 in N3092) as if the code is:
This in turn results in a dangling reference. The temporary object’s life-time is not extended because your operator+ doesn’t return a prvalue. Returning the objects by value is perfectly fine. It’ll create temporary objects but these objects are rvalues, so we can steal their resources to make it very effective.
Secondly, your code should also not compile for the same reason this won’t compile:
Inside the function’s body x is an lvalue and you can’t initialize the “return value” (in this case the rvalue reference) with an lvalue expression. So, you’d need an explicit cast.
Thirdly, you’re missing an const&+const& overload. In case both of your arguments are lvalues, the compiler won’t find a usable operator+ in your case.
If you don’t want so many overloads, you could also write:
I intentionally didn’t write
return value+=x;because this operator probably returns an lvalue reference which would have led to copy construction of the return value. With the two lines I wrote the return value will be move constructed from value.At least this case is very efficient because there is no unnecessary copying involved even if the compiler isn’t able to elide the copies – thanks to a move-enabled string class. Actually, with a class like std::string you can exploit its fast swap member function and make it effective in C++03 as well provided you have a reasonably smart compiler (like GCC):
See David Abraham’s article Want Speed? Pass by Value. But these simple operators won’t be as effective given:
Here the left hand side of the operator is always an lvalue. Since operator+ takes its left hand side by value this leads to many copies. The four overloads from above deal perfectly with this example, too.
It’s been a while since I read Linus’ old rant. If he was complaining about unnecessary copies with respect to std::string, this complaint is no longer valid in C++0x, but it was hardly valid before. You can efficiently concatenate many strings in C++03:
But in C++0x you can also use operator+ and std::move. This will be very efficient, too.
I actually looked at the Git source code and its string management (strbuf.h). It looks well thought through. Except for the detach/attach feature you get the same thing with a move-enabled std::string with the obvious advantage that the resource it automatically managed by the class itself as opposed to the user who needs to remember to call the right functions at the right times (strbuf_init, strbuf_release).