Why doesn't std::cin choke on this?

W

William Payne

Hello, when I was writing a user-driven test program for a data structure I
wrote, I encountered an annoying problem. The test program is laid out as a
menu with several numbered options. The user selects one option by typing in
its corresponding number (int). Depending on the choice he made, he may be
asked to provide another integer (or no input at all). So all the user does
is entering integers. I don't want the user to be able to cause problems or
crashes by providing invalid input, but he is despite my error checking.
Here follows output from my program here boiled down to a "minimum":
$ ./test
1 - choice one
2 - choice two
3 - exit program
What is your choice? 1
Enter integer to be doubled: 2+2
2 doubled is 4
1 - choice one
2 - choice two
3 - exit program
What is your choice? Enter integer to be squared:

In this test run the user presses 1 when asked for which choice he wants in
the menu. But when asked to input an integer he enters 2+2, and cin does not
go into an error state but leaves the last '2' plus a newline in the stream,
triggering a new menu option (the '+' was eaten by a call to cin.get() which
is supposed to get rid of the newline. Now how can I fix my code so it
doesn't allow this? And why doesn't cin choke on 2+2 when asked to enter an
integer?

#include <iostream>
#include <limits>
#include <string>

int enter_integer(const std::string& prompt);

int main()
{
std::string prompt;

while(true)
{
std::cout << "1 - choice one" << std::endl;
std::cout << "2 - choice two" << std::endl;
std::cout << "3 - exit program" << std::endl;

int n = enter_integer("What is your choice? ");

switch(n)
{
case 1:
n = enter_integer("Enter integer to be doubled: ");

std::cout << n << " doubled is " << n * 2 << std::endl;

break;
case 2:
n = enter_integer("Enter integer to be squared: ");

std::cout << n << " squared is " << n * n << std::endl;

break;
case 3:
return 0;
default:
std::cout << n << " is an invalid menu choice." << std::endl;
}
}
}

int enter_integer(const std::string& prompt)
{
while(true)
{
std::cout << prompt << std::flush;

int n;

std::cin >> n;

if(!std::cin)
{
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(),
'\n');

std::cout << "You must enter an integer!" << std::endl;
}
else
{
std::cin.get(); /* Get rid of newline in the stream. */

return n;
}
}

return -1; /* Should never be reached. */
}

/ William Payne
 
K

Karl Heinz Buchegger

William said:
Hello, when I was writing a user-driven test program for a data structure I
wrote, I encountered an annoying problem. The test program is laid out as a
menu with several numbered options. The user selects one option by typing in
its corresponding number (int).

And this is your problem. You read an int.
The simplest thing you can do is to forget the idea of reading an int.
After all, if the user types 2, this is a character just like 'a' or 'b'.
So read everything as character, it simpler then checking the stream for it's
state, getting rid of the invalid input, clearing that error state and thus
make the stream workable again.
Depending on the choice he made, he may be
asked to provide another integer (or no input at all).

Having no input at all is tricky when you try to read an int. Again: If you
read in a string, this becomes trivial.
So all the user does
is entering integers. I don't want the user to be able to cause problems or
crashes by providing invalid input, but he is despite my error checking.
Here follows output from my program here boiled down to a "minimum":
$ ./test
1 - choice one
2 - choice two
3 - exit program
What is your choice? 1
Enter integer to be doubled: 2+2
2 doubled is 4
1 - choice one
2 - choice two
3 - exit program
What is your choice? Enter integer to be squared:

In this test run the user presses 1 when asked for which choice he wants in
the menu. But when asked to input an integer he enters 2+2, and cin does not
go into an error state

why should it?
Streams are defined that they try to read as much as possible to fullfil the
request. The request was to read an int, thus everything that can be an int
is taken from the stream, in your case the first '2'. The rest is left in the
input queue and is waiting for your program to read it.
but leaves the last '2' plus a newline in the stream,
triggering a new menu option (the '+' was eaten by a call to cin.get() which
is supposed to get rid of the newline. Now how can I fix my code so it
doesn't allow this?

look up the ignore() function of the stream.
But again, it's simpler to *not* read the users input as an int. Read it as a string,
then try to extract the number from the string. This way the user can enter whetever
he likes (even no 'numer' at all), and nothing special will happen: The users input
is read as string and your number detection function will not be able to extract
a number from that and thus your program will emit an error. But in no case the
stream has entered a fail state and must be made useable again (for extracting
something from a string, the stringstreams are a handy tool).
And why doesn't cin choke on 2+2 when asked to enter an
integer?

See above. It's defined to be that way. If you had entered: '*2' it would
have choked, because '*' is not a valid starting character for an integer.
 
V

Victor Bazarov

William Payne said:
Hello, when I was writing a user-driven test program for a data structure I
wrote, I encountered an annoying problem. The test program is laid out as a
menu with several numbered options. The user selects one option by typing in
its corresponding number (int). Depending on the choice he made, he may be
asked to provide another integer (or no input at all). So all the user does
is entering integers. I don't want the user to be able to cause problems or
crashes by providing invalid input, but he is despite my error checking.
Here follows output from my program here boiled down to a "minimum":
$ ./test
1 - choice one
2 - choice two

Whoever designed this user interface should be shot. Why not write

1 - to double an integer
2 - to square an integer

?
3 - exit program
What is your choice? 1
Enter integer to be doubled: 2+2
2 doubled is 4
1 - choice one
2 - choice two
3 - exit program
What is your choice? Enter integer to be squared:

In this test run the user presses 1 when asked for which choice he wants in
the menu. But when asked to input an integer he enters 2+2, and cin does not
go into an error state but leaves the last '2' plus a newline in the stream,
triggering a new menu option (the '+' was eaten by a call to cin.get() which
is supposed to get rid of the newline. Now how can I fix my code so it
doesn't allow this? And why doesn't cin choke on 2+2 when asked to enter an
integer?

Read the FAQ section 15. 15.2 and 15.3 apply, IMO.

Victor
 
K

Karl Heinz Buchegger

Karl said:
And this is your problem. You read an int.
The simplest thing you can do is to forget the idea of reading an int.
After all, if the user types 2, this is a character just like 'a' or 'b'.
So read everything as character,

Sorry, make that: as string. This also accounts for your user to enter 'abcdefg'
in response to 'Enter a number'.
 
W

William Payne

Victor Bazarov said:
[snip]
1 - choice one
2 - choice two

Whoever designed this user interface should be shot. Why not write

1 - to double an integer
2 - to square an integer

?

[snip]

LOL, I wrote that and I don't know what I was thinking. The real program has
a menu laid out as you want, though.

[snip]
Read the FAQ section 15. 15.2 and 15.3 apply, IMO.

Victor

I read those sections as you suggested, and I have decided to store the
initial input in a std::string rather than an int and perform error checking
on the string (and converting to int if valid).

Thanks for your reply.

/ William Payne
 
W

William Payne

Karl Heinz Buchegger said:
Sorry, make that: as string. This also accounts for your user to enter 'abcdefg'
in response to 'Enter a number'.

Thanks for your reply, Karl. I will do as you suggested and read the user
input into a std::string.

/ William Payne
 
W

William Payne

William Payne said:
out
as a If fullfil an cin.get() it
as a string, The
users input to

Thanks for your reply, Karl. I will do as you suggested and read the user
input into a std::string.

/ William Payne

I have now written a small code snippet that prompts the user to enter an
integer, a snippet which should reject any input that is not a valid
integer. It seems to work but I would like to hear some comments on it if
you please.

int enter_integer(const std::string& prompt)
{
while(true)
{
std::cout << prompt << std::flush;

int n;

std::string input;

/* Maybe use cin.getline() instead and complain if the user *
* enters, for example, 2 2, instead of just silently dis- *
* everything left in stream like we do now? */
std::cin >> input;

std::cin.ignore(std::numeric_limits<std::streamsize>::max(),
'\n');

if(!check_if_valid_integer(input))
{
std::cout << input << " is not a valid integer." << std::endl;
}
else
{
std::stringstream ss;

ss << input;

/* check_if_valid_integer() doesn't catch cases where the user just
*
* enters '+' or '-', so we need this check here. Maybe we should
*
* check for that in check_if_valid_integer as well.
*/
if((ss >> n))
{
return n;
}
else
{
std::cout << input << " is not a valid integer." << std::endl;
}
}
}

return -1; /* Should never be reached. Maybe throw exception? */
}

bool check_if_valid_integer(const std::string& s)
{
if(!isdigit(s[0]) && s[0] != '+' && s[0] != '-')
{
return false;
}

return count_if(s.begin() + 1, s.end(), isdigit) == 0;
}

Hope this post doesn't go unnoticed, since I decided to reply to this thread
instead of starting a new one.

/ William Payne
 
N

Nick Hounsome

William Payne said:
Hello, when I was writing a user-driven test program for a data structure I
wrote, I encountered an annoying problem. The test program is laid out as a
menu with several numbered options. The user selects one option by typing in
its corresponding number (int). Depending on the choice he made, he may be
asked to provide another integer (or no input at all). So all the user does
is entering integers. I don't want the user to be able to cause problems or
crashes by providing invalid input, but he is despite my error checking.
Here follows output from my program here boiled down to a "minimum":
$ ./test
1 - choice one
2 - choice two
3 - exit program
What is your choice? 1
Enter integer to be doubled: 2+2
2 doubled is 4
1 - choice one
2 - choice two
3 - exit program
What is your choice? Enter integer to be squared:

In this test run the user presses 1 when asked for which choice he wants in
the menu. But when asked to input an integer he enters 2+2, and cin does not
go into an error state but leaves the last '2' plus a newline in the stream,
triggering a new menu option (the '+' was eaten by a call to cin.get() which
is supposed to get rid of the newline. Now how can I fix my code so it
doesn't allow this? And why doesn't cin choke on 2+2 when asked to enter an
integer?

Other replies have pointed out why it doesn't work but I think you will find
that the right answer is not to use a string or at least
not as in cin >> s.

All programs of this type are line oriented and hence you need to read a
line -
1. use the function getline to read a whole line into a std::string.
2. Make your line the basis of an istringstream, iss
3. read your integer with if( iss >> i)
4. skip whitespace with iss >> std::ws
5. if iss.get() is not now EOF then there was some input you didn't ask for

This does the biz without you explicitly parsing strings and also stops the
user entering extra newlines (remember that all formatted input functions
eat whitespace first and newlines are whitespace).
 
C

Chris \( Val \)

[Please snip out any text not relevant to your reply, thanks]

| I have now written a small code snippet that prompts the user to enter an
| integer, a snippet which should reject any input that is not a valid
| integer. It seems to work but I would like to hear some comments on it if
| you please.

[snip]

| Hope this post doesn't go unnoticed, since I decided to reply to this thread
| instead of starting a new one.

Oh, it was noticed alright :).

Having said that, I have modified your code a little:

bool check_if_valid_integer( const std::string& s )
{
static const char* ValidChars( "0123456789" );

return s.find_first_not_of( ValidChars ) == std::string::npos;
}

int enter_integer( const std::string& prompt )
{
while( true )
{
std::cout << prompt << std::flush;

int n;

std::string input;
std::getline( std::cin, input );

if( !check_if_valid_integer( input ) )
{
std::cout << input << " is not a valid integer." << std::endl;
return -1;
// Note: '-1' is a valid integer, it is up to you if you
// want to accept it - throwing an exception may be of
// better here.
}
else
{
std::istringstream ss( input );

if( ss >> n )
return n;
}
}
}

Cheers.
Chris Val
 
D

Duane Hebert

bool check_if_valid_integer( const std::string& s )
{
static const char* ValidChars( "0123456789" );

return s.find_first_not_of( ValidChars ) == std::string::npos;
}

wouldn't this be better named check_if_valid_unsigned_integer() ?
To check for valid integer, you need to allow '-' but only in the first postion
no? Why not just use strstream functions?
 
C

Chris \( Val \)

| <snip>
| > bool check_if_valid_integer( const std::string& s )
| > {
| > static const char* ValidChars( "0123456789" );
| >
| > return s.find_first_not_of( ValidChars ) == std::string::npos;
| > }
|
| wouldn't this be better named check_if_valid_unsigned_integer() ?
| To check for valid integer, you need to allow '-' but only in the first postion
| no? Why not just use strstream functions?

Your right(but I didn't want to change it), that's why
I mentioned it, but still allowed it to return a negative
value for the error code.

I should have been a little more clear :).

Cheers.
Chris Val
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top