What is the expected behavior of tellg() when std::stringstream is constructed with ("some content", std::ios::in|std::ios::ate)?
Asked Answered
A

2

7

I have the following piece of code that surprised me (using libstdc++4.8)...

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main() {
    std::string s("some content");
    std::stringstream ss(s, std::ios::in|std::ios::ate);
    std::istream& file = ss;
    //ss.clear(); Makes no difference...
    std::cout << "tellg() pos: " << file.tellg() << std::endl;
    return 0;
}

... which has the following output.

tellg() pos: 0

This behaviour is different to when using std::ifstream(std::ios::ate).

  • Is this behaviour correct/expected?
  • Does one need to explictly seekg(0, std::ios::end) despite opening with ate?
  • clearing the state doesn't make a difference.
  • Please note that the string has content.
Afrikaner answered 5/4, 2016 at 13:1 Comment(0)
L
2

This is fully in-line with what standard tells us. Here are relevant details:

The version of constructor you've used:

Constructs a std::basic_stringbuf object by calling the default constructor of std::basic_streambuf, initializes the character sequence with an empty string, and sets the mode to which, than followed by initializing the associated character sequence as if by calling str(new_str).

Default constructor of basic_stringbuf is not interesting here, and than std::basic_stringbuf::str:

Deletes the entire underlying character sequence of this std::basic_stringbuf and then configures a new underlying character sequence containing a copy of the contents of s. ... For append streams (mode & ios_base::ate == true), pptr() == pbase() + s.size(), so that subsequent output will be appended to the last character copied from s

And finally, tellg(), which calls pubseekoff on the buffer:

If which includes ios_base::in and this buffer is open for reading (that is, if ((which & ios_base::in) == ios_base::in), then repositions the read pointer std::basic_streambuf::gptr: .. then newoff is the current position of the pointer (gptr()-eback() in this case)

To sum it up: since you didn't modify get position in any way (constructor only modifies put position), it returns 0.

Lass answered 5/4, 2016 at 13:34 Comment(3)
This is an interesting decision. The effect is that for tests that rely on a factory returning iostream, the behavior of creating a fstream(mode) and a stringstream(mode) object in input only mode (ios::in) differs. I've used std::stringstream for simulating "in memory" files in tests, and code mostly rely on either istream, ostream or iostream. The difference in behavior made my tests fail unexpectedly.Afrikaner
Also, for me here is a slight lack of "Is-A" relationship. Granted, a filestream is not a stringstream, but I feel there would have been value in making the behaviour of ios::in | ios::ate more "heterogeneous"Afrikaner
Well, exactly. File stream is not string stream, and there is no 'is-a' relationship between them. So I do not see an immediate defect in standard. Yet, I would admit, I do not see why get pointer can't be updated for string buffer as well when str() is called. You might want to submit a change proposal for this.Lass
T
2

In short:

tellg() returns gptr()-eback() and providing the ios_base::in flag in the stringstream (and therefore the basic_stringbuf) constructor has the postcondition gptr() == eback().

Thus, 0 is expected / enforced.

In long:

  • tellg() returns rdbuf()->pubseekoff(0, std::ios_base::cur, std::ios_base::in)
  • rdbuf() returns const_cast<basic_stringbuf*>(&sb).
  • pubseekoff(0, std::ios_base::cur, std::ios_base::in) calls seekoff(0, std::ios_base::cur, std::ios_base::in)
  • seekoff returns gptr()-eback()
  • eback() returns a pointer to the beginning of the get area
  • gptr() returns the current get point

stringstream Initialization:

basic_stringstream (basic_string const &str, ios_base::openmode which);

Effects: Constructs an object of class basic_stringstream, initializing the base class with basic_iostream(&sb) and initializing sb with basic_stringbuf(str, which).

 

basic_stringbuf(basic_string const &str, ios_base::openmode which)

Effects: Constructs an object of class basic_stringbuf, initializing the base class with basic_streambuf(), and initializing mode with which. Then calls str(s).

 

void basic_stringbuf::str(const basic_string<charT,traits,Allocator>& s);

Effects: Copies the content of s into the basic_stringbuf underlying character sequence and initializes the input and output sequences according to mode.

Postconditions:

  • If mode & ios_base::out is true, pbase() points to the first underlying character and epptr() >= pbase() + s.size() holds;

  • if mode & ios_base::ate is true, pptr() == pbase() + s.size() holds, otherwise pptr() == pbase() is true.

  • If mode & ios_base::in is true, eback() points to the first underlying character, and both gptr() == eback() and egptr() == eback() + s.size() hold.

Where the last bit is relevant: providing ios_base::in has the postcondition gptr() == eback() and since tellg() returns gptr()-eback() the result is required to be zero.

Tachometer answered 5/4, 2016 at 14:6 Comment(2)
"If mode & ios_base::in is true, eback() points to the first underlying character" ... does not reflect intuitive behavior, IM(h)O.Afrikaner
@WernerErasmus: A file has one "position pointer" where stringstream has two. One for reading and one for writing. I don't see an immediate benefit of having both of the at the end when providing ate Imo it is actually a good thing that you can use ate to append to the string while still being able to read from the stringstream from the beginning. The only thing that is a bit counterintuitive to me is the fact that a string initialized strinstream{str} (without ate) does not append to str when using operator<< but otoh there is no atb to reverse that behaviour if default.Tachometer

© 2022 - 2024 — McMap. All rights reserved.