Skip to main content
added 75 characters in body
Source Link
Fred Nurk
  • 501
  • 2
  • 7
  • 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), and know the specifics of their implementation that allow what they want, 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".

  • 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".

  • 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), and know the specifics of their implementation that allow what they want, 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".

edited body; added 529 characters in body
Source Link
Fred Nurk
  • 501
  • 2
  • 7
  • 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, pluxplus 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.

In order to handle all manipulators, such as std::endl, add:

struct Formatter {
  Formatter& operator<<(std::ios_base& (*manip)(std::ios_base&)) {
    ss_ << manip;
    return *this;
  }
  Formatter& operator<<(std::ios& (*manip)(std::ios&)) {
    ss_ << manip;
    return *this;
  }
  Formatter& operator<<(std::ostream& (*manip)(std::ostream&)) {
    ss_ << manip;
    return *this;
  }
};
  • 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.
  • 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, plus 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.

In order to handle all manipulators, such as std::endl, add:

struct Formatter {
  Formatter& operator<<(std::ios_base& (*manip)(std::ios_base&)) {
    ss_ << manip;
    return *this;
  }
  Formatter& operator<<(std::ios& (*manip)(std::ios&)) {
    ss_ << manip;
    return *this;
  }
  Formatter& operator<<(std::ostream& (*manip)(std::ostream&)) {
    ss_ << manip;
    return *this;
  }
};
Source Link
Fred Nurk
  • 501
  • 2
  • 7

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. :)