In addition to what's already been said, I would:
Mark the stringstream as public. This won't affect most uses of your code, and can already be hacked around with a custom manipulator to get at the "internal" stream object, but it will enable those that need to access the internal stream (such as to avoid the string copy inherent in the stringstream interface) to do so. Of course, 0x move semantics allay much of this need, but are still Not Quite Here Yet™.
Check the stream before returning the string; if it's in a failed state, throw an exception (or at least log the condition somewhere before returning a string). This is unlikely to occur for most uses, but if it does happen, you'll be glad you found out the stream is failed rather than screw with formatting while wondering why "it just won't work".
Regarding double-assignment, there's no assignment at all. The sequence points should be mostly what people expect, but, exactly, it looks like:
some_function(((Formatter() << expr_a) << expr_b) << expr_c);
// 1 2 3
The operators order it as if it was function calls, so that:
- Formatter() and expr_a both occur before the insertion marked 1.
- The above, plus insertion 1, plus expr_b happen before insertion 2.
- The above, plus insertion 2, plux expr_c happen before insertion 3.
- Note this only limits in one direction: expr_c can happen after expr_a and before Formatter(), for example.
- Naturally, all of the above plus the string conversion occur before calling some_function.
To add to the discussion on temporaries, all of the temporaries created are in the expression:
some_function(Formatter() << make_a_temp() << "etc.")
// one temp another temp and so on
They will not be destroyed until the end of the full expression containing that some_function call, which means not only will the string be passed to some_function, but some_function will have already returned by that time. (Or an exception will be thrown and they will be destroyed while unwinding, etc.)
I've used this pattern several times to wrap streams, and it's very handy in that you can use it inline (as you do) or create a Formatter variable for more complex manipulation (think a loop inserting into the stream based on a condition, etc.). Though the latter case is only important when the wrapper does more than you have it do here. :)