I have a feeling ya'll get plenty of cin questions here and are quit tired of them, but I have a few that are a little more in depth. I started trying to write my own input stream flush template and had some very minor successes when I found the sticky at the top of this and found a break through. Here is what I wrote after the breakthrough

template <typename CharT>
void flush(std::basic_istream<CharT> &in)
{
	in.clear();
	char nextChar= in.get();
	while(nextChar != '\n' && nextChar != std::char_traits<CharT>::eof())
		nextChar= in.get();
	in.clear();
}

template <typename CharT, typename var_type>
void extractr(std::basic_istream<CharT> &in, var_type &var, bool FLUSH= false)
{
	if(!in)
		flush(in);
	in >> var;
	while(!in)
	{
		cout << "\nInvalid data type, please enter a " << typeid(var).name() << endl << "-> ";
		flush(in);	
		in >> var;
	}
	if(FLUSH)
		flush(in);
}

I played around with the solution the sticky offered and got to this, but it doesn't work in my codes.

template <typename CharT>
void ignore_line(std::basic_istream<CharT> &in)
{
	in.clear();
	if(in.rdbuf()->sungetc() != std::char_traits<CharT>::eof() && in.get() != '\n')
		in.ignore(std::numeric_limits<std::streamsize>::max(), in.widen('\n'));
	in.clear();
}

Question 1) What exactly does widen() do? I've seen it several times but don't really understand what it is accomplishing.

Question 2) Why does the first one work perfectly but the second one not fix the input stream?

Recommended Answers

All 6 Replies

>Question 1) What exactly does widen() do?
It takes the character you pass and converts it to the locale the stream is imbued with. For example, you want to convert a character literal to the appropriate wchar_t value if the stream is wide oriented, but leave it as is if the stream is not wide oriented.

>Question 2) Why does the first one work perfectly
>but the second one not fix the input stream?
It's hard to say without seeing how you're testing the code, but I'd guess you're immediately entering invalid data. Because no characters have been successfully extracted, sungetc returns eof() and the ignore isn't performed. The effect is the same as if the stream were empty.

The first function works because you're extracting characters and not relying on the previously extracted character, which might not exist for this particular usage, but it basically negates the solution provided in the second function because you're back to blocking reads.

I've played around with some inputs and such looking at it from that point of view and it makes sense. I still don't fully understand why it remains in an error state, even if the ignore() is ignored.

void main()
{
	flush(cin);
	char c;
	cout << "char? ";
	extractr(cin, c);
	cout << "char: " << c << endl;
	int var;
	cout << "int? ";
	extractr(cin, var);
	cout << "int: " << var << endl;
}

When using flush, this will continue to ask for a corrected input until it receives one, but it will pause at the beginning of the program because it needs something for in.get(). When using ignore_line, this will ask for corrected input once after receiving incorrect int input (or using leftover from something like "asdf" for char input) but will enter an infinite loop (due to error state in cin) after any further incorrect input. Two questions again, why does it behave like that? and is there a usable and possibly portable solution?

>I still don't fully understand why it remains in an error state, even if the ignore() is ignored.
It doesn't remain in an error state, but because the bad data isn't extracted, you immediately enter the same error state again as soon as you say in >> var; in extractr after trying to ignore the line.

>why does it behave like that?
If the stream is empty, sungetc will return eof(). If the stream is not empty and the last extracted character is not '\n', the entire line will be ignored. Here's what happens with "asdf" as the first input and "hjkl" as the second input:

  1. 'a' is extracted by the first call of extractr.
  2. 's' causes the second call of extractr to fail (it's not an int) and because the last extracted character was 'a', the condition in ignore_line is met and ignore is called. This extracts the rest of the line and leaves the stream empty.
  3. extractr prompts for correct input, but the next character is 'h', which fails because it isn't an int. However, because the stream is empty, sungetc returns eof() and the ignore isn't performed. Nowhere else in the error loop of extractr do you clear out bad characters, so the loop will run infinitely by erroring on 'h'.

>is there a usable and possibly portable solution?
Add another level of abstraction where you can count the number of times ignore_line fails to clear the stream. If it does so more than a set number of times, which you would specify as the threshold for an infinite loop, force ignore to be called:

template <typename CharT>
bool flush ( std::basic_istream<CharT>& in, bool always_flush )
{
  in.clear();

  if ( always_flush
    || in.rdbuf()->sungetc() != std::char_traits<CharT>::eof()
    && in.get() != '\n' )
  {
    in.ignore ( std::numeric_limits<std::streamsize>::max(), in.widen ( '\n' ) );
    return true;
  }

  in.clear();

  return false;
}

template <typename CharT>
void flush ( std::basic_istream<CharT>& in )
{
  int failed = 0;

  while ( !flush ( in, false ) ) {
    if ( ++failed > 1 ) {
      flush ( in, true );
      break;
    }
  }
}

First of all, thank you so much for all of this help, it's really helping me understand all of this.

Second, I did a lot more looking into it and reading, and I think I came up with something (closely related to your ignore and pause classes as I looked at those quite a bit).

template <typename CharT>
std::streamsize ignore_line( std::basic_istream<CharT> &in, bool always_ignore= false )
{
	std::streamsize nread= 0;

	in.clear();

	if( always_ignore || ( in.rdbuf()->sungetc() != std::char_traits<CharT>::eof() && in.get() != '\n' ) )
	{
		in.ignore( std::numeric_limits<std::streamsize>::max(), in.widen('\n') );
		nread= in.gcount();
	}

	in.clear();

	return nread;
}

template <typename var_type>
class extract
{
	var_type *var;
	bool _always_flush, _full_count;
	mutable std::streamsize *_nread;
public:
	extract( var_type &input, bool always_flush= false )
		: _always_flush(always_flush), _nread(0), var(&input), _full_count(false)
	{}

	extract( var_type &input, std::streamsize &nread, bool always_flush= false )
		: _always_flush(always_flush), _nread(&nread), var(&input), _full_count(false)
	{*_nread=0;}

	//_nread[0] holds pre-extraction character ignore count
	//_nread[1] holds mid-extraction character ignore count
	//_nread[2] holds post-extraction character ignore count
	extract( var_type &input, std::streamsize nread[3], bool always_flush= false )
		: _always_flush(always_flush), _nread(nread), var(&input), _full_count(true)
	{_nread[0]=0;_nread[1]=0;_nread[2]=0;}

	template <typename CharT>
	friend std::basic_istream<CharT> &operator>> ( std::basic_istream<CharT> &in, const extract &manip )
	{
		int _ignore_count= 0;

		if( manip._always_flush || !in )
			manip._nread[0]+=ignore_line(in);
		
		in >> *manip.var;
		while(!in)
		{
			cout << "\nInvalid data type, please enter a value of type: "
				 << typeid(*manip.var).name() << "\n-> ";
			_ignore_count+= ignore_line(in, true);
			in >> *manip.var;
		}

		if(manip._full_count)
		{
			manip._nread[1]= _ignore_count;
			_ignore_count= 0;
		}

		if(manip._always_flush)
			_ignore_count+= *manip._nread= ignore_line(in);

		if(manip._full_count)
			manip._nread[2]= _ignore_count;

		return in;
	}
};

void main()
{
	int i;
	char c;
	string s;
	std::streamsize chars[3];

	cout << "char? ";
	cin >> extract<char>(c);
	cout << "char: " << c << endl;
	cout << "int? ";
	cin >> extract<int>(i, chars, true);
	cout << "int: " << i << endl << "ignored: " << chars[0] << ' ' << chars[1] << ' ' << chars[2] << endl;
	cout << "string? ";
	cin >> extract<string>(s);
	cout << "string: " << s << endl;
}

It works well, although, I am unsure about the parts involving _nread. If there is a better way to do this, please let me know. Also, whenever I enter "asdf" for the first input, the first ignore count comes out to be 1 when it seems to me that it should be 4 (ignoring 'sdf\n'). Again, thank you so much for all of this, I think I'm understanding all of these concepts quite a bit better now.

>whenever I enter "asdf" for the first input, the first ignore count comes
>out to be 1 when it seems to me that it should be 4 (ignoring 'sdf\n')
Run it through a debugger and check the values returned by ignore_line. I imagine you'll find them to be correct, and that should help you pinpoint the real problem.

Found it...Thanks so much for all your help Narue. I think I have a better grasp on all of this now.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.