Dear lazyweb,

Please explain to me why the following code works the way it does. From looking at the following code and stringstream::str(), stringstream::str(string) docs the behavior of the following code does not make sense to me.

#include <sstream>
#include <iostream>

using namespace std;

int main(int argc, char**) {
stringstream ss(”foo”);
cout << ss.str() << endl;
ss << “bar”;
cout << ss.str() << endl;
ss << “more”;
cout << ss.str() << endl;
}

Why is doing << after str(string) causing this stringstream to loose the initialization string? What possible API usecase would justify such behavior?

For the curious, output is:

foo
bar
barmore

It seems that the only sensible way to use stringstream is to do ss.str(”") unless you want to have your initial data reset for no reason. In that case why add a weird method overload instead of a .reset() method.

Update: Note that stringstream ss(”foo”) is equivalent to stringstream ss; ss.str(”foo”);

9 Responses to “Where is the sanity in the C++ std library?”

  1. jmdesp Says:

    I strongly suspect

    string s(”foo”);
    stringstream ss(s);

    would give you the expected behaviour.

    Do you see where the difference lies ?

    Yes, it would probably make more sense if “ss << “bar”;” did throw an exception in the original version.

  2. tglek Says:

    jmdesp,
    you suspect wrong :)

  3. Nico Says:

    Dear lazyglek,

    str() resets the stream position. Add

    ss.seekp(0, ios_base::end);

    after a call to str() to reset te stream position back to the end: http://codepad.org/TBd4o9aT

  4. Joe Says:

    But Nico,

    str() is called after the initial operator<< as well. Shouldn’t that also reset the stream position to the beginning?

  5. tglek Says:

    Nico,
    Thank you for the excellent answer. Seems odd that str(string&) doesn’t imply a .seekp, but I’ll live with that now that you’ve uncovered the reason for this mess.

  6. Nico Says:

    Joe, excellent point. The correct explanation is that stringstream’s constructor does initialize its buffer with the constructor’s argument (”foo”) but does not set the seek position to something else than 0. This is completely independent of str(), and my previous explanation was wrong.

    See also http://codepad.org/eW0uqVZD .

  7. Luke Wagner Says:

    Ah, that is confusing. The issue isn’t with stringstream::str() at all, but with the stringstream constructor. If you run:

    stringstream s1(”foo”), s2;
    s2 << “foo”;
    cout << s1.tellp() << ‘,’ << s2.tellp() << ‘\n’;

    you can see the discrepancy: the ‘put’ pointer is being set to the first character by the constructor.

    I thought this might be a bug, but 27.7.1.1 explicitly says this is the what should happen *unless* the ios_base::ate (at-end) bit is set in the constructor args. (The default is ios_base::in | ios_base::out). In retrospect, this makes sense because it is the same behavior you would get when you open a fstream for output. Thus, the following constructor call gives you achieves the intended goal of the snippet in the post:

    stringstream ss(”foo”, ios_base::in | ios_base::out | ios_base::ate);

  8. tglek Says:

    Luke,
    This seems somewhat reasonable, but as I put in the update, same behavior occurs with stringstream ss;ss.str(”foo”). That doesn’t seem right as there are no more flags to change the behavior

  9. Luke Wagner Says:

    That is also odd. It appears that stringstream::str() has been defined to be like fstream::open(): both change the contents of the underlying buffer, and both do not modify the ‘put’ pointer unless the at-end bit is set. Thus, the following gives the desired results:

    stringstream ss(ios_base::in | ios_base::out | ios_base::ate);
    ss.str(”foo”);

    So really, all this confusion has been caused by the fact that ios_base::ate is not a default flag for ostreams. I wonder if there is a good reason not to have it set, because it seems more intuitive.

Leave a Reply