tags:

views:

541

answers:

2

When running the following code and enter a number, it works fine. But when entering a letter, the program enters an infinite loop, displaying "Enter a number (0 to exit): cin failed."

My intent was to handle the cin fail case and prompt the user again.

int number;
do{
    cout << "Enter a number (0 to exit): ";
    cin >> number;
    if(cin.fail()){
        cout << "cin failed." << endl;
        cin.clear();
    }else{
        cout << "cin succeeded, " << number << " entered." << endl;
    }
}while(number != 0);
+3  A: 

You need to clear the line from cin, using cin.ignore, in addition to clearing the stream state (which is what cin.clear does).

I have several utility functions to make this easier (you'll be interested in clearline in particular, which clears the stream state and the current line) and almost an exact example of what you want.

Your code, more or less, using my clearline:

#include "clinput.hpp" // move my file to a location it can be used from

int main() {
  using namespace std;
  while (true) {
    cout << "Enter a number (0 to exit): ";
    int number;
    if (cin >> number) {
      cout << "Read " << number << '\n';
      if (number == 0) {
        break;
      }
    }
    else {
      if (cin.eof()) { // tested only *after* failed state
        cerr << "Input failed due to EOF, exiting.\n";
        return 1;
      }
      cerr << "Input failed, try again.\n";
      clearline(cin); // "cin >> clearline" is identical
    }
  }
  return 0;
}

There is still a potential issue here (fixed in my clinput_loop.cpp with blankline), with leaving input in the buffer that will screw up later IO (see "42 abc" in the sample session). Extracting the above code into a separate and self-contained function is left as an exercise for the reader, but here's a skeleton:

template<class Type, class Ch, class ChTr>
Type read(std::basic_istream<Ch,ChTr>& stream, Ch const* prompt) {
  Type value;
  // *try input here*
  if (could_not_get_input or more_of_line_left) {
    throw std::runtime_error("...");
  }
  return value;
}
template<class Type, class Ch, class ChTr>
void read_into(
  Type& value,
  std::basic_istream<Ch,ChTr>& stream,
  Ch const* prompt
) {
  value = read<Type>(stream, prompt);
}

Example use:

int n;
try {
  read_into(n, std::cin, "Enter a number: ");
}
catch (std::runtime_error& e) {
  //...
  raise;
}
cout << "Read " << n << '\n';

clearline function extracted for posterity, in case above links ever break (and slightly changed to make self-contained):

#include <istream>
#include <limits>

template<class C, class T>
std::basic_istream<C,T>& clearline(std::basic_istream<C,T>& s) {
  s.clear();
  s.ignore(std::numeric_limits<std::streamsize>::max(), s.widen('\n'))
  return s;
}

The template stuff is a bit confusing if you're not used to it, but it's not hard:

  • std::istream is a typedef of std::basic_istream<char, std::char_traits<char> >
  • std::wistream is a typedef of std::basic_istream<wchar_t, std::char_traits<wchar_t> >
  • widen allows '\n' to become L'\n' as appropriate
  • this code works for both of the common char and wchar_t cases, but also any compatible instantiation of basic_istream
  • it's written to be called as clearline(stream) or stream >> clearline, compare to other manipulators like std::endl, std::ws, or std::boolalpha
Roger Pate
Thanks! That fixed it. One additional point is that I had to use both, cin.clear() and cin.ignore().
Joshua Moore
Yes, if you look at my clearline function (in clinput.hpp, and follow the code path), it calls both clear and ignore.
Roger Pate
+1 for obvious reason
Kornel Kisielewicz
I think this is a good answer, with a great explanation of this small issue (but I'm not biased, eh? :P); however, I want to emphasize that **the iostream interface is horrible** (but the implementation is fast enough, contrary to popular complaint, once you add move semantics where needed) and **I wish there was a widespread, free (both beer and freedom), better alternative to recommend**.
Roger Pate
+1  A: 

This is probably what you intended to do:

#include <iostream>
using namespace std;

int main ()
{
  int i;
  do {
    if (cin.fail())
    {
      cin.ignore(255);
      cin.clear();
    }          
    cout << "Please enter an integer value: ";
    cin >> i;
  } while ( cin.fail() );
  cout << "The value you entered is " << i;
  return 0;
}
Kornel Kisielewicz
This will always result in an infinite loop, as you neither modify the stream state nor the stream buffer once it enters a failed state.
Roger Pate
"Always result.." if bad input is entered the first time, that is.
Roger Pate
Heh, seems one learns something new each day... now it is correct? (no compiler at hand to test...)
Kornel Kisielewicz
Use the *numeric\_limits* form from my answer instead of a magic constant like 255, as the value I use is guaranteed to be interpreted as infinity by the stream. You must also specify the newline delimiter.
Roger Pate