In my simple Fraction class, I have the following method to get user input for the numerator, which works fine for checking garbage input like garbage, but will not recognize user input which starts with an integer, and is followed by garbage, 1 garbage or 1garbage.
void Fraction::inputNumerator()
{
int inputNumerator;
// loop forever until the code hits a BREAK
while (true) {
std::cout << "Enter the numerator: ";
// attempt to get the int value from standard input
std::cin >> inputNumerator;
// check to see if the input stream read the input as a number
if (std::cin.good()) {
numerator = inputNumerator;
break;
} else {
// the input couldn't successfully be turned into a number, so the
// characters that were in the buffer that couldn't convert are
// still sitting there unprocessed. We can read them as a string
// and look for the "quit"
// clear the error status of the standard input so we can read
std::cin.clear();
std::string str;
std::cin >> str;
// Break out of the loop if we see the string 'quit'
if (str == "quit") {
std::cout << "Goodbye!" << std::endl;
exit(EXIT_SUCCESS);
}
// some other non-number string. give error followed by newline
std::cout << "Invalid input (type 'quit' to exit)" << std::endl;
}
}
}
I saw a few posts on using the getline method for this, but they didn't compile when I tried them, and I'm having trouble finding the original post, sorry.
Better check as follows:
// attempt to get the int value from standard input
if(std::cin >> inputNumerator)
{
numerator = inputNumerator;
break;
} else { // ...
Or yes: Follow recommendations to parse a complete input line combining std::getline() and std::istringstream appropriately.
Related
In my case, I have to make sure the user input is either 1 or 2, or 3.
Here's my code:
#include <iostream>
using namespace std;
void invalid_choice_prompt() {
string msg = "\nInvalid Command! Please try again.";
cout << msg << endl;
}
int ask_user_rps_check_input(int user_choice) {
if (user_choice == 1 || user_choice == 2 || user_choice == 3) return 1;
return 0;
}
int ask_user_rps() {
// ask user's choice of Rock or Paper or Scissors
while (1) {
string msg =
"\nPlease enter your choice:\nRock - 1\nPaper - 2\nScissors - 3";
cout << msg << endl;
int user_choice;
cin >> user_choice;
if (ask_user_rps_check_input(user_choice)) {
return user_choice;
}
invalid_choice_prompt();
}
}
int main() {
ask_user_rps();
return 0;
}
The code is capable to handle the situation when the input is an integer, but when the input are characters or strings, the program will be trapped in the infinite loop.
Is there any elegant way to do this? I've found some methods about using cin.ignore to ignore the specified length of io buffer, but I don't think this method is flexible enough. I am looking for a more flexible solution.
I think an option would be to collect the user input to a string and then move it to stringstream using getline kind of like this:
std::string input;
std::getline(std::cin, input);
//Now check if the input is correct. if it is, then:
std::stringstream stream;
stream << input;
int num;
stream >> num;
I'm not sure if this is a good method but it works.
One of the simplest solution would be to check the cin stream failure something like below:
int ask_user_rps() {
// ask user's choice of Rock or Paper or Scissors
while (1) {
string msg =
"\nPlease enter your choice:\nRock - 1\nPaper - 2\nScissors - 3";
cout << msg << endl;
int user_choice;
cin >> user_choice;
if(cin.fail()) {
invalid_choice_prompt();
std::cin.clear();
std::cin.ignore(256,'\n');
continue;
}
if (ask_user_rps_check_input(user_choice)) {
return user_choice;
}
invalid_choice_prompt();
}
}
Reading from a stream using operator >> takes as many characters from the stream as the target type accepts; the rest will remain in the stream for subsequent reads. If the input has a format error (e.g. a leading alphabetical characters when an integer is expected), then an error-flag is set, too. This error-flag can be checked with cin.fail(). It remains set until it gets explicitly cleared. So if your code is...
int user_choice;
cin >> user_choice;
and if you then enter something that is not a number, e.g. asdf, then user_choice has an undefined value, an error-flag cin.fail() is (and reamins) set. So any subsequent read will fail, too.
To overcome this, you have to do three things:
First, check the error-flag. You can do this either through calling cin.fail() after a read attempt of through checking the return value of the expression (cin >> user_choice), which is the same as calling cin.fail().
Second, in case of an error, you need to clear the error-flag using cin.clear(). Otherwise, any attempt to read in anything afterwards will fail.
Third, if you want to continue with reading integral values, you need to take the invalid characters from the stream. Otherwise, you will read in asdf into a variable of type integer again and again, and it will fail again and again. You can use cin.ignore(numeric_limits<streamsize>::max(),'\n'); to take all characters until EOF or an end-of-line from the input buffer.
The complete code for reading an integral value with error-handling could look as follows:
int readNumber() {
int result;
while (!(cin >> result)) {
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(),'\n');
cout << "Input is not a number." << std::endl;
}
return result;
}
Take input as char
string user_choice;
cin >> user_choice;
check input is valid or not if(user_choice=='1')
Under windows10 and VS2017:
I was trying to read a double number 1.1 from keyboard using istream and put it into a int type variable, say temp. In reason temp is 1 but the istream seems to be stuck in some error status. In expectancy istream should stop and wait for keyboard input but it continues another round read-from-buffer and error occurs this time.
I had checked the rdstate() and it was equal to 2 after the 2nd round read-from-buffer. I know it was abnormal but why?
To replicate, run the code, type 1.1 in console and hit enter, the error will show up.
Actually, I used int32 to try to store double for some reasons. The program is supposed to print valid input from keyboard. Here valid refers to that the input should not exceed the range of int32 or be double/readable character. Otherwise the program should print Invalid input on the screen.
#include <iostream>
std::istream& f(std::istream &in) {
int temp = 0;
while(true) {
while (in >> temp) {
if (temp == -1) {
break;
}
std::cout << temp << std::endl;
}
if (in.eof()|| temp == -1) break;
if (!in) {
std::cout << "Invalid input" << std::endl;
in.clear();
in.ignore(10000,32);
}
}
in.seekg(0, std::ios::beg);
return in;
}
int main(){
std::cout << "Please input some integers and end with ^Z or -1" << std::endl;
f(std::cin);
return 0;
}
Keep in mind that when you read 1.1 from the keyboard you're reading text. The program looks at that text and decides what value it represents, depending on the type of the variable that you're reading into. If you're reading into an int, the input routine reads the first '1', then sees '.', which can't be part of the text representation of an int, and it stops reading. Your variable gets the value 1. If you try to read another int from the same input stream, that '.' will stop the read immediately, since it can't be part of an int, and the attempted input fails.
Short answer: don't do that. If your input text looks like floating-point, read it as floating-point.
Try this:
#include <iostream>
std::istream& f(std::istream &in) {
std::string temp = "";
while(true) {
while (in >> temp) {
if (temp == "-1") {
break;
}
std::cout << temp << std::endl;
}
if (in.eof()|| temp == "-1") break;
if (!in) {
std::cout << "Invalid input" << std::endl;
in.clear();
in.ignore(10000,32);
}
}
in.seekg(0, std::ios::beg);
return in;
}
int main(){
std::cout << "Please input some integers and end with ^Z or -1" << std::endl;
f(std::cin);
return 0;
}
You are parsing character by character from the buffer. You cannot put a character into an integer. You are assuming you are reading 1.1 from the stream, but you're instead reading 1,.,1 from the buffer, and the . is throwing the error. The above portion works as you are reading characters and keeping them in a string.
Made something like this:
int main()
{
while (true)
{
std::cout << "Enter a number between one and nine. \n";
int oneandnine;
std::cin >> oneandnine;
if (std::cin.fail())
{
std::cin.clear();
std::cin.ignore(100, '\n');
std::cout << "INVALID UNPUT!\n";
}else if (oneandnine <= 9 && oneandnine >= 1)
{
break;
}else
{
std::cout << "INVALID UNPUT!\n";
}
}
return 0;
}
and when input is provided something like this 456aihdb, getting something like this:
INVALID UNPUT!
Enter a number between one and nine.
INVALID UNPUT!
Enter a number between one and nine.
Why does it loop twice like this? is it because when the 456 is discarded and the rest aihdb isn't which causes it to loop again and skip a cin input?
It is exactly as you think it is.
The fail flag isn't set immediately, instead the formatted input operator reads the integer 456 into oneandnine, but doesn't set the fail flag since it's a valid integer value. That leads to the else case executing since std::cin.fail() is false and oneandnine is not between 1 and 9.
The next iteration you read the invalid input and the fail flag will be set leading to the second error output.
One common way to handle validation is to read the whole line into a string, put that string into an std::istringstream and use that to attempt to parse the input:
if (!std::getline(std::cin, line))
{
// Failure of some kind, could be EOF or something else
// Probably best not to continue in this case
}
std::istringstream iss(line);
if (!(iss >> oneandnine))
{
// Invalid input, report it as such
}
if (oneandnine < 1 || oneandnine > 9)
{
// Invalid number, report it as such
}
// Correct input, continue with program
Note that input such as 6abc will be considered valid by the above code. The 6 will be extracted into oneandnine and the abc part will silently be discarded. If that's not wanted there are other ways for the parsing (e.g. std::stoi or std::strtol if exceptions are not wanted). Do that instead of the >> extraction, but the rest of the code above should be fine.
std::istream's operator >> doesn't read in whole lines. It reads until it finds an invalid character or whitespace, if it has found a valid character before the invalid character the read operation succeeds and the invalid character is left in the stream.
In your example the first iteration successfully reads 456 and leaves aihdb in the stream. This fails your range check and the second iteration then tries to read the remaining characters which fails as the first character isn't a number.
If you want to read whole lines use std::getline then parse the whole line into a number. For example:
#include <iostream>
#include <string>
using std::cout;
int main()
{
while (true)
{
std::cout << "Enter a number between one and nine. \n";
std::string line;
std::getline(std::cin, line);
int oneandnine;
size_t pos;
try
{
oneandnine = std::stoi(line, &pos);
}
catch ( std::exception& )
{
oneandnine = -1;
}
if (pos != line.size() || oneandnine > 9 || oneandnine < 1)
{
std::cout << "INVALID INPUT!\n";
}
else
{
break;
}
}
return 0;
}
I thought I understood handling bad input with cin.clear() and cin.ignore(), like it is explained here, but in the following example
#include <iostream>
#include <limits>
using namespace std; //I know that this isn't good practice.
int main () {
int a, b;
while (cout << "Input some int: " && !(cin >> a)) {
cout << "Wrong datatype!\n";
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
while (cout << "Input some int: " && !(cin >> b)) {
cout << "Wrong datatype!\n";
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
}
if (a > 1) cout << "Some event.\n";
if (b > 1) cout << "Some other event.\n";
return 0;
}
the behavior I want is only present when the unwanted input is some character.
So if I enter x and y, I will again be asked for two ints and get the appropriate outputs, same if I enter a char and an int two times.
However: If I input, say, 2.3, I will get
Input some int: Wrong datatype!
but won't have a chance to correct my input, since the result invariantly outputs "Some event." The second prompt just accepts the float right away.
What's happening, actually, is the 2 in 2.3 is being accepted by the first prompt, leaving .3 in the input buffer. The Wrong datatype! you are seeing is from your second prompt, seeing a ., which is not a valid character for an integer. You then, I assume, enter an integer which is accepted by your second prompt.
This fundamental approach is fragile, and error-prone.
Your obvious intent is to accept a line of input, and process it. If so, then the correct function to do that is std::getline(). That's what its purpose is. That's exactly what it does. The >> operator does not do that. That's not what it's for. Of course, by using the various auxiliary methods, like ignore(), and clear(), one can still achieve that goal, but, as you've discovered, using those functions correctly is not intuitive. Of course, you can spend copious time pouring over their documentation to understand their every semantic behavior, but why bother, when you can simply use std::getline(), and then move on to something else. It's simply easier to do that.
Of course, once a line of input is received, you would like to parse it into an integer. Now is the correct time to use >> to parse it:
std::string line;
if (std::getline(line, std::cin))
{
std::istringstream i{line};
int n;
if (i >> n)
{
// Input parsed
}
}
Isn't this simpler, more straightforward, and less of a gotcha?. Of course, entering "2.3" here will result in the >> operator parsing the "2", and succeeding, leaving ".3" unparsed. If you would like to detect this situation, simply use get() to see what's left in the std::istringstream. Perhaps accept any trailing whitespace, if you wish.
The problem here is when you enter something like 2.3 to a int cin is okay with that. It reads the 2, sees the . so it stops reading and stores the 2 in the variable and leaves the .3 in the buffer for the next call. So, you pass the first loop, get to the second loop, and then you fail as it tries to read in the . into b. Then you clear the .3 and you can enter another input. If you enter another 2.3 the same thing will happen and b will get 2 and the program continues on.
The "bullet proof" way to read in input is to read it in as a std::string and then parse that to make sure the full input was good. That would look like
std::string line;
while (cout << "Input some int: " && std::getline(cin, line)) {
std::stringstream ss(line);
ss >> a;
if (ss.eof()) // we did consume all the input
break;
else
cout << "Wrong datatype!\n";
}
while (cout << "Input some int: " && std::getline(cin, line)) {
std::stringstream ss(line);
ss >> b;
if (ss.eof()) // we did consume all the input
break;
else
cout << "Wrong datatype!\n";
}
When you input "2.3", cin will stop at '.', and interpret '2' as the desired input.
Then, you will clear cin, when the '.' is encountered, discarding 3.
If you then input a new integer, it will accept it.
Many answers here suggest the use of std::getline and string parsing, either using the string functions or stringstreams. This is quite inefficient and not the way the streams are supposed to be used.
Instead, parse the data when it is still in the input stream:
#include <iostream>
#include <cctype>
#include <limits>
struct read_int {
int& a;
read_int(int& aa) : a{ aa } { }
friend std::istream& operator >>(std::istream& is, read_int& ri) {
char delim;
while(!(is >> ri.a) || (delim = is.get(), delim != '\n' && !std::isspace(delim))) {
std::cerr << "Bad!\n";
is.clear();
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
return is;
}
};
int main() {
int a, b;
std::cin >> read_int(a) >> read_int(b);
std::cout << a << ' ' << b;
return 0;
}
This function will accept input like "4 5" or "4\n6" alike, but requests a new input for data like "4.2", discarding everything read before.
Here is a simple program that demonstrates this issue.
#include <iostream>
using namespace std;
double d;
int main() {
cin >> d;
if (cin.fail()) {
cout << "Input is invalid" << endl;
}
else {
cout << d << endl;
}
return (0);
}
Entering a value like adsfasdf or !##$!*##(&Adf prints Input is invalid as expected. Entering a numeric value like 13.52 returns 13.52, also as expected.
But entering something that starts out as a number, but follows with strings, such as 13.52asdfasdf!##$!##$!#A prints 13.52. So why does that kind of input not print Input is invalid?
How do I get this input to be treated as invalid, instead of just printing the numeric part?
In C++, when you try to read a value out of a stream, it will read as much as possible from the stream as long as it matches the expected format, leaving everything else untouched. If the read wasn't able to read any values at all, then it sets the fail bit on the stream. This explains why if you enter total garbage you get an error, whereas if you enter a number followed by garbage you'll read valid data, but still have garbage left in the stream.
One way to get around this is to use the std::getline function to read a line of text from cin, which will pick up everything the user typed in, and to then parse it by running it through a std::istringstream. Here's one way to do this, based on some code that we used to use at Stanford in our intro classes:
int readInt() {
while (true) {
std::string input;
std::getline(cin, input); // Read a line of text from the user
/* Set up an istringstream to read the data */
std::istringstream converter(input);
/* Try to read an int. If we succeed, make sure there wasn't
* any trailing data.
*/
int value;
if (converter >> value) {
char leftover;
if (converter >> leftover) {
std::cout << "Please enter an integer." << std::endl;
} else {
return value;
}
} else {
std::cout << "Please enter an integer." << std::endl;
}
}
}
You can easily customize / templatize this to read data of whatever type seems most appropriate.