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?
#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()
{
constint 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());
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.
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"
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?
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.
#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.
#include <iostream>
#include <string>
usingnamespace 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
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:
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();
}