Where is the sanity in the C++ std library?
July 8th, 2008
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”);
July 8th, 2008 at 11:11 am
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.
July 8th, 2008 at 11:14 am
jmdesp,
you suspect wrong
July 8th, 2008 at 11:56 am
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
July 8th, 2008 at 12:11 pm
But Nico,
str() is called after the initial operator<< as well. Shouldn’t that also reset the stream position to the beginning?
July 8th, 2008 at 12:21 pm
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.
July 8th, 2008 at 12:24 pm
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 .
July 8th, 2008 at 3:27 pm
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);
July 8th, 2008 at 3:34 pm
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
July 8th, 2008 at 10:13 pm
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.