Only Accept Numeric Character in std::cin

If for example '32a' was accidentally entered instead of '32', I want the program to reject this and ask for the input again, instead of accepting the '32' part and ignoring the 'a'. If I am asking for integer input and anything but an integer is inputted, then the program should consider everything in the input buffer to be erroneous and therefore ask the user again for an integer.

But frustratingly, std::cin will just truncate the 'a' off the end and accept the '32' as the input. How can I stop this from happening so that the program considers any input that isn't an integer to be invalid?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int People()
{
	while(true)
	{
		int people;

		std::cout << "How many people do you know?\n";
		std::cin  >> people;

		if (std::cin.fail()) 
		{
			std::cin.clear(); 
			std::cin.ignore(32767, '\n'); 
		}
		else 
		{
			std::cout << "You know " << people << " people?\n";
			std::cin.ignore(100, '\n'); 
			return(people); 
		}
	}
}

int main()
{
	int peers = People();
	
}

Output:
How many people do you know?
'32a'
You know 32 people?
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <cctype>

int People()
{
    std::cout << "How many people do you know? ";

    int people ;
    // if a non-negative integer is immediately followed white space
    if( std::cin >> people && std::isspace( std::cin.peek() ) && people >= 0 )
        return people ; // return it

    std::cout << "invalid input. try again\n" ;
    std::cin.clear();
    std::cin.ignore( 32767, '\n' );
    return People() ;
}

int main()
{
    const int peers = People();
    std::cout << "peers: " << peers << '\n' ;
}
By the way 'a' isn't truncated from '32a', it's left in the stream, so successive cin extraction operations will still read that 'a' and fail. So from that observation, cin + int would never work.

So since you cannot read int using cin, because it would fail, that's not an option. You could read the input as a string and then check whether the string is a number, if it is - then you could convert it into a number otherwise you can reprompt the user.
1
2
3
4
5
6
7
8
9
10
11
std::string str;
do {
   cin >> str;
   for (auto& i : str) {
      if ( !(isdigit(i) ) {
         str.clear();
         std::cout << "Please type an integer";
         break;
      }
   }
} while (str.empty());
JLBorges
1
2
if( std::cin >> people && std::isspace( std::cin.peek() ) && people >= 0 )
        return people ; // return it 

So are non-numeric characters determined to be whitespace characters by the std::isspace() function?
Ah I think I got it now... if the next character held in the input stream is a whitespace character then this means that the input is valid. But if the next character held in the input stream is not a whitespace character then this means that there is a non-numeric character left over in the input stream, which essentially means that the input was invalid.
Last edited on
When you try to input 52a to an int using cin, a\n will be left in the buffer.

When you try to input 52 to an int using cin, \n will be left in the buffer.

So JLBorges has cleverly used that to identify that when the next character in buffer is a \n, the input was read properly.
BTW "52 a" would be considered as proper input "52"
Last edited on
I think "52 a" would fail because of space.

Yes you're right, this does fail.
Last edited on
Then instead of isspace you can use a function to return whether a char is \n, if it's important to you.
peek() looks at the next character in the input stream, the one immediately after the integer was read. We verify that that character is a white space character (typically space, tab, new line etc.).
peek() looks at the next character in the input stream, the one immediately after the integer was read. We verify that that character is a white space character (typically space, tab, new line etc.).

Is there any way we can iterate through peek() to read all remaining characters in the input stream, rather than just the next character? Or is there a way to just check if the input stream is empty?
Last edited on
To my knowledge (which isn't vast):

calioranged wrote:
Is there any way we can iterate through peek()
No, I don't think you can traverse stdin like that, you can only know the next character in the stream and the stream must have a '\n' in it for peek() to function. Alas.

calioranged wrote:
Or is there a way to just check if the input stream is empty?
You can read from the stdin stream and assess what was extracted, I don't know any other way.
Sorry I missed this thread earlier.

This is a common question, and one that has a fairly easy answer, but is also fairly esoteric and not well-known. It comes in two parts:

    (1) Users will always press ENTER at the end of every requested input.
        (In other words, use std::getline() to get std::strings as input.)

    (2) Try to convert the input. Accept it if it passes. Reject it if it fails.
        (This is where the simple trick lies.)

Here is a little function I make regular use of. Bonus: comes with example program!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <stdexcept>
#include <sstream>
#include <string>

template <typename T>
bool string_to( const std::string& s, T& result )
{
  // Convert string argument to T
  std::istringstream ss( s );
  ss >> result >> std::ws;
 
  // Stream should be in good standing with nothing left over
  return ss.eof();
}

template <typename T>
T string_to( const std::string& s )
{
  T result;
  if (!string_to( s, result ))
    throw std::invalid_argument( "T string_to<T> ()" );
  return result;
}

int main()
{
  std::cout << "Please enter nothing to stop entering stuff.\n";
  while (true)
  {
    std::string s;
    std::cout << "Enter an integer: ";
    getline( std::cin, s );
    if (s.empty()) break;
    
    int n;
    if (!string_to( s, n ))
      std::cout << "What? That's not an integer!\n";
    else
      std::cout << "Good job! You entered \"" << n << "\".\n";
  }
}

See the trick there on lines 11 and 14? Use a stream's conversion ability to skip leading whitespace and try to convert something, and then see if you can find the end of the stream without hitting anything but trailing whitespace. This is what streams are designed to do.

Enjoy!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <string>
using namespace std;

int getMyInt()
{
   size_t pos;
   size_t *ptr = &pos;
   string test;
   int i;

   cout << "Input an integer (or 0 to end): ";
   getline( cin, test );

   try
   {
      i = stoi( test, ptr );            // ptr will point to next position in string
   }
   catch( ... )                         // Failure to convert anything
   {
      cout << "Not convertible to int\n";
      return getMyInt();
   }

   if ( pos < test.size() )             // Additional chars (including blanks) after
   {
      cout << "Spurious characters at end\n";
      return getMyInt();
   }
   
   return i;
}


int main()
{
   int value = 1;
   while ( value != 0 )
   {
      value = getMyInt();
      cout << "Integer is " << value << '\n';
   }
}


Input an integer (or 0 to end): 42
Integer is 42
Input an integer (or 0 to end):      42
Integer is 42
Input an integer (or 0 to end): 42 and more
Spurious characters at end
Input an integer (or 0 to end): 42e5
Spurious characters at end
Input an integer (or 0 to end): abc
Not convertible to int
Input an integer (or 0 to end): 24
Integer is 24
Input an integer (or 0 to end): -24
Integer is -24
Input an integer (or 0 to end): 0
Integer is 0
Last edited on
Duthomhas said
See the trick there on lines 11 and 14? Use a stream's conversion ability to skip leading whitespace and try to convert something, and then see if you can find the end of the stream without hitting anything but trailing whitespace. This is what streams are designed to do.

Yes that is a good way of handling this conundrum. I had been trying to find ways of converting a string to an integer from std::cin, so thanks for that.

Another solution that I figured out is actually quite a simple one:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int People()
{
	while (true)
	{
		int people;
		std::cout << "How many people do you know?\n";
		std::cin  >> people;

		if (std::cin.fail() || std::cin.peek()!='\n')
		{
			std::cin.clear();
			std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
			std::cout << "You entered an invalid character. Please try again.\n";
		}
		else // didn't fail and next character in input stream is new line ('\n')
		{
			std::cout << "You know " << people << " people?\n";
			return(people);
		}
		std::cout << std::endl;
	}
}

int main()
{
	int peers = People();
}
Last edited on
Topic archived. No new replies allowed.