Where is the sanity in the C++ std library?

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”);

10 comments

  1. 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. jmdesp,
    you suspect wrong :)

  3. 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. But Nico,

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

  5. 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. 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. 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. 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. 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.

  10. @ Luke Wagner
    I am not sure what you were referring to when you said “ostreams” in your last post.

    I took a look following links:
    http://www.google.com/search?q=ios_base%3A%3Aate+is+not+a+default+flag+for+ostreams
    http://stdcxx.apache.org/doc/stdlibug/30-3.html
    http://www.google.com/search?q=ios_base%3A%3Aate
    http://www2.roguewave.com/support/docs/sourcepro/edition9/html/stdlibug/30-3.html
    http://gcc.gnu.org/ml/libstdc++/2000-q1/msg00381.html
    http://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-3.3/classstd_1_1ios__base.html
    http://www.cppreference.com/wiki/io/sstream/start

    Since
    http://www.cppreference.com/wiki/io/sstream/start
    says that stringstream allows both input and output. I consider that a bidirectional stream
    and

    Bidirectional file streams, on the other hand, do not have the flag set implicitly. This is because a bidirectional stream does not have to be in both input and output mode in all cases. You might want to open a bidirectional stream for reading only or writing only. Bidirectional file streams therefore have no implicit input or output mode. You must always set a bidirectional file stream’s open mode explicitly.”
    @http://www2.roguewave.com/support/docs/sourcepro/edition9/html/stdlibug/30-3.html
    , might be the reason, that it isn’t a default flag for stringstream ss variable.