In the case of a program expecting some number, you assume cooperation of the user. For instance, consider this program asking for the age and then for the favorite number of the user:
#include <iostream>
int main() {
int age = 0;
int favorite_number = 0;
std::cout << "Enter your age: ";
// Extract formatted input into the target variable
std::cin >> age;
std::cout << "Enter your favorite number: ";
std::cin >> favorite_number;
std::cout << "Age: " << age << ", Favorite Number: " << favorite_number << "\n";
return 0;
}
If you try running this program and entering a valid number for the age and for the favorite number, everything works great. However, if you enter a word instead of a number when asked for your age, the behavior of the program becomes quite weird. It prints the prompt for the favorite number but instantly skips past it, and starts printing zeros without waiting for any other input.
The newline is hardly ever missing. It is waiting right at the place where the next read did not expect it.
Recovery from Input Failure
This is happening due to the fact that C++ streams have some internal state flags telling you about success or failure of the last operation. In the case described, the program tries to read an integer but receives letters instead, and the extraction fails. After failure of extraction, C++ sets the variable to zero and puts the input stream in the failure state.
Usually recovering from an invalid numeric input requires two steps: clearing of the stream flags and clearing of the input buffer containing the characters causing the conversion to fail. While clearing only the flags would allow you to perform another extraction, the same bad character would be waiting for the extraction to start once again. On the other hand, while ignoring the characters only while the stream is in the failure state would not help either.
Once the input stream goes into the failure state, it becomes blocked for any further input operation. Any subsequent formatted or line-based input operation will be silently ignored, and that is why the second prompt was skipped in the program above. To recover from the failure state, you should explicitly detect the failure, reset the stream and discard the invalid data from the input buffer.
You can test the failure state using std::cin.fail(), which returns true on the error. Once you've detected the failure, you need to call std::cin.clear() to clear the flags and return the stream back to the healthy state. However, clearing the flags does not remove the bad input. The invalid word is still waiting for you in the input buffer. If you try reading another integer right away, the program will try reading the same word and fail again.
In order to discard the bad characters, you should use std::cin.ignore() with two arguments: the maximum number of characters to discard and the delimiter character stopping the discarding process. For example, std::cin.ignore(10000, '\n') tells the stream to discard up to 10,000 characters or up to the newline character, whichever comes first. This discards the rest of the current input line.
Here is the way to implement this kind of recovery procedure to prompt the user until he provides a valid integer:
#include <iostream>
int main() {
int age = 0;
std::cout << "Enter your age: ";
std::cin >> age;
// Check this condition before entering the block
if (std::cin.fail()) {
std::cout << "That was not a valid number!\n";
std::cin.clear();
std::cin.ignore(10000, '\n');
std::cout << "Please try entering your age again: ";
std::cin >> age;
}
std::cout << "Your age is: " << age << "\n";
return 0;
}
Line based parsing could make things easier in the validation process. Reading a whole line into a std::string and then parsing this string would keep one user's response from interfering with the following prompt. It would also let the program reject the trailing characters and distinguish the empty response from the end-of-file that might be somewhat confusing for the formatted extraction.
By resetting the stream with std::cin.clear() and discarding the bad line with std::cin.ignore(), the stream is fully reset and the second input attempt will be successful.
Partial Input and Trailing Characters
Another frequent input issue occurs when the user enters more information than your program expects. If the prompt asks for a quantity and the user enters 5 boxes, the extraction operation std::cin >> quantity is successful and stores the number 5. However, the space, the word boxes and the newline character are waiting in the input buffer.
If later your program tries to ask for the name of an item using std::getline(), the leftover characters would be read right away instead of waiting for new input from the keyboard. In order to prevent this problem, you need to discard the input buffer right after reading a number if you are going to read a text later in the program.
#include <iostream>
#include <string>
int main() {
int quantity = 0;
std::cout << "Enter quantity: ";
std::cin >> quantity;
// Discard leftover input before reading the next line
std::cin.ignore(10000, '\n');
std::string item_name;
std::cout << "Enter item name: ";
std::getline(std::cin, item_name);
std::cout << "You ordered " << quantity << " of " << item_name << "\n";
return 0;
}
If the user inputs a line of text longer than the limit you choose, the remaining part of this line would still wait in the buffer. Although some large fixed value could do the trick in case of manual keyboard input, production-grade application should use the maximum limit provided by the standard library's <limits> header.
Exercise: Common Console Input ProblemsStarter code, hints and solutionOpenClose
Practice: Common Console Input Problems
Work through the mission, reveal hints only when needed, and compare your result with the model solution.
Your challenge
Test abc followed by 21. Explain the failed extraction and retry, then add a check so a second invalid attempt prints an error and exits instead of reporting an age.
Mission checklist
- Run the starter as a baseline, then predict the change requested in Your challenge.
- Make that change in your editor; use the lesson-specific hints if you need help.
- Compile and run your answer, then compare with the worked solution and explain the result.
Starter program
This is the working baseline, not the completed answer. Compile as C++20, then make the change described above.
#include <iostream>
int main() {
int age = 0;
std::cout << "Enter your age: ";
std::cin >> age;
// Check this condition before entering the block
if (std::cin.fail()) {
std::cout << "That was not a valid number!\n";
std::cin.clear();
std::cin.ignore(10000, '\n');
std::cout << "Please try entering your age again: ";
std::cin >> age;
}
std::cout << "Your age is: " << age << "\n";
return 0;
}Hints
Enter letters where age expects an integer and follow the std::cin.fail() branch. clear() repairs the stream state; it does not remove the bad characters.
After clearing and ignoring the failed first input, test the retry extraction itself. If it fails again, print the requested error and return a nonzero exit status.
Worked solution
The sample first supplies abc, which fails integer extraction. clear repairs the stream state and ignore discards the invalid line; the second input, 21, becomes age. This version also checks the retry, so another invalid entry is not reported as a valid age.
#include <iostream>
int main() {
int age = 0;
std::cout << "Enter your age: ";
std::cin >> age;
// Check this condition before entering the block
if (std::cin.fail()) {
std::cout << "That was not a valid number!\n";
std::cin.clear();
std::cin.ignore(10000, '\n');
std::cout << "Please try entering your age again: ";
std::cin >> age;
}
if (!std::cin) {
std::cout << "The second entry was invalid too.\n";
return 1;
}
std::cout << "Your age is: " << age << "\n";
return 0;
}
Example input
abc
21
Example output
Program output only; your terminal may also echo the input you type.
Enter your age: That was not a valid number!
Please try entering your age again: Your age is: 21
0 comments
No comments yet. Be the first.