Subclassing ifstream.

Hello. I am creating a subclass of ifstream. It's sole purpose is to create a new function peek(n), which allows me to peek n characters ahead instead of just 1 character which the peek() function already does. I just need it so that I can roll my own LALR(2).

Anyways, it's not exactly an extension of peek(). I want peek(n) to return a character, but I don't want it to effect the ifstream state bits.

So here's the problem. I implement peek(n) using seekg and tellg. However, it appears once seekg() returns back EOF, I can't reset the read pointer to what it was so that subsequent get() call returns what you would expect.

So, I need help. Here's what I have for a file ('hello.txt'):

"Hello World!\n"

And here's the test output:

$ ./a.exe hello.txt
Read char: 'H' Peek(2) : 'l'
Read char: 'e' Peek(2) : 'o'
Read char: 'l' Peek(2) : ' '
Read char: 'l' Peek(2) : 'W'
Read char: 'o' Peek(2) : 'o'
Read char: ' ' Peek(2) : 'r'
Read char: 'W' Peek(2) : 'l'
Read char: 'o' Peek(2) : 'd'
Read char: 'r' Peek(2) : '!'
Read char: 'l' Peek(2) : '
'
Read char: 'd' Peek(2) : '�'
Read char: '�' Peek(2) : '�'


Notice that in the first column of chars it is missing '!', and '\n'.

So how to build code:

g++ pifstream.cc pifstream.hh -Dpifstream_test -g


So here's the code:

/* pifstream.hh

An extension of ifstream that adds the function peek(n).
*/
#include <iostream>
#include <fstream>

#ifndef pifstream_hh
#define pifstream_hh

using std::ifstream;
using std::streampos;

class pifstream : public ifstream {
public:
pifstream( const char *fname, ios_base::openmode mode = ios_base::in) : ifstream( fname, mode) {}
public:
int peek(int n);
};




#endif // pifstream_hh



/* pifstream.cc
*/

#include "pifstream.hh"

//using ifstream::iostate;

int pifstream::peek(int n) {
// If n == 0|1, works just like peek().
// If it is greater, as expected, except, old internal state is preserved.

int cread;
streampos oldpos;
streampos newpos;
iostate oldstate;

if( n == 1 || n == 0)
return ifstream::peek();


oldpos = tellg();
oldstate = rdstate();
seekg( n, ios_base::cur);

if( rdstate() & ifstream::eofbit) {
cread = -1;
clear(); // This doesn't seem to help anything.
seekg( oldpos);
}
else {
cread = get();
seekg( -(n+1), ios_base::cur);
}

clear( oldstate);

return cread;
}




#ifdef pifstream_test
using namespace std;

int main(int argc, char *argv[]) {

if( argc < 2) {
cout << "Need filename." << endl;
return 1;
}

pifstream input( argv[1]);
int creader;
int cpeek2 = 32;

while( input) {
creader = input.get();
cpeek2 = input.peek(2);

cout << "Read char: '" << (char) creader << "'\t"
<< "Peek(2) : '" << (char) cpeek2 << "'" << endl;
}

return 0;
}


#endif // pifstream_test



Anyways, thanks!
So I figured it out. It turns out the code is largely okay, but I had to make a change in the following lines. So from:


if( !rdstate() & ifstream::eofbit) { // CHANGED THIS
cread = -1;
clear(); // This doesn't seem to help anything.
seekg( oldpos);
}


if( !(rdstate() & ifstream::goodbit)) {
cread = -1;
clear(); // This doesn't seem to help anything.
seekg( oldpos);
}
A few things.

It seems to me that you should only return ifstream::peek() if n = 0, not if n = 1 or above. 0 is the next char and 1 is the one after that etc...

1
2
	if(n == 0)
		return ifstream::peek();

Also I don't see why you need to check the stream after the read, it makes more sense to peek() the stream and return it to its original state regardless:
1
2
3
4
5
6
7
8
9
10
11
	// preserve initial state
	oldstate = rdstate();
	oldpos = tellg();

	// peek relevant char
	seekg(n, ios_base::cur);
	cread = ifstream::peek();

	// restore original state
	seekg(oldpos);
	clear(oldstate);

Last, your read loop needs to check the stream *after* it reads in the char:
1
2
3
4
5
6
7
8
9
10
11
12

	pifstream input("input.txt");
	char creader;
	char cpeek2;

	while(input.get(creader)) // only enter the body if this succeeds
	{
		cpeek2 = input.peek(2);

		cout << "Read char: '" << (char) creader << "'\t" << "Peek(2) : '"
			<< (char) cpeek2 << "'" << endl;
	}

Otherwise you will process the EOF marker.
Last edited on
Thanks Galik.

I thought my correction worked, but it doesn't now. I'm not sure why. Anyways, I tried your's and that works! So, I'm not quite sure if I'm out of the woods.

Regarding if n==0, just call peek(), yeah, I saw that too, but I didn't want to get into it when I posted my first example. But great pair of eyes you got there! :)

I guess I check the results of the read because I wanted to either

seekg(-n, ios_bas::cur) or
seekg( oldpos)

I was / am worred about the performance of each. Anyways, I probably shouldn't worry about performance. I should just get it to work.

Topic archived. No new replies allowed.