Stream states
The ios_base class contains several state flags that are used to signal various conditions that may occur when using streams:
Flag | Meaning |
---|---|
goodbit | Everything is okay |
badbit | Some kind of fatal error occurred (e.g. the program tried to read past the end of a file) |
eofbit | The stream has reached the end of a file |
failbit | A non-fatal error occurred (eg. the user entered letters when the program was expecting an integer) |
Although these flags live in ios_base, because ios is derived from ios_base and ios takes less typing than ios_base, they are generally accessed through ios (eg. as std::ios::failbit).
ios also provides a number of member functions in order to conveniently access these states:
Member function | Meaning |
---|---|
good() | Returns true if the goodbit is set (the stream is ok) |
bad() | Returns true if the badbit is set (a fatal error occurred) |
eof() | Returns true if the eofbit is set (the stream is at the end of a file) |
fail() | Returns true if the failbit is set (a non-fatal error occurred) |
clear() | Clears all flags and restores the stream to the goodbit state |
clear(state) | Clears all flags and sets the state flag passed in |
rdstate() | Returns the currently set flags |
setstate(state) | Sets the state flag passed in |
The most commonly dealt with bit is the failbit, which is set when the user enters invalid input. For example, consider the following program:
1 2 3 |
std::cout << "Enter your age: "; int age; std::cin >> age; |
Note that this program is expecting the user to enter an integer. However, if the user enters non-numeric data, such as “Alex”, cin will be unable to extract anything to age, and the failbit will be set.
If an error occurs and a stream is set to anything other than goodbit, further stream operations on that stream will be ignored. This condition can be cleared by calling the clear() function.
Input validation
Input validation is the process of checking whether the user input meets some set of criteria. Input validation can generally be broken down into two types: string and numeric.
With string validation, we accept all user input as a string, and then accept or reject that string depending on whether it is formatted appropriately. For example, if we ask the user to enter a telephone number, we may want to ensure the data they enter has ten digits. In most languages (especially scripting languages like Perl and PHP), this is done via regular expressions. However, C++ does not have built-in regular expression support (it’s supposedly coming with the next revision of C++), so typically this is done by examining each character of the string to make sure it meets some criteria.
With numerical validation, we are typically concerned with making sure the number the user enters is within a particular range (eg. between 0 and 20). However, unlike with string validation, it’s possible for the user to enter things that aren’t numbers at all -- and we need to handle these cases too.
To help us out, C++ provides a number of useful functions that we can use to determine whether specific characters are numbers or letters. The following functions live in the cctype header:
Function | Meaning |
---|---|
std::isalnum(int) | Returns non-zero if the parameter is a letter or a digit |
std::isalpha(int) | Returns non-zero if the parameter is a letter |
std::iscntrl(int) | Returns non-zero if the parameter is a control character |
std::isdigit(int) | Returns non-zero if the parameter is a digit |
std::isgraph(int) | Returns non-zero if the parameter is printable character that is not whitespace |
std::isprint(int) | Returns non-zero if the parameter is printable character (including whitespace) |
std::ispunct(int) | Returns non-zero if the parameter is neither alphanumeric nor whitespace |
std::isspace(int) | Returns non-zero if the parameter is whitespace |
std::isxdigit(int) | Returns non-zero if the parameter is a hexadecimal digit (0-9, a-f, A-F) |
String validation
Author's note
From here on out, we make use of features that aren’t (yet) covered in the tutorials. If you’ve got a good grip of C++, you might be able to understand what these features do based on their names and the way they’re used. We advise you to look up the new functions and types in a reference to get a deeper understanding of what they do how what else they can be used for.
Let’s do a simple case of string validation by asking the user to enter their name. Our validation criteria will be that the user enters only alphabetic characters or spaces. If anything else is encountered, the input will be rejected.
When it comes to variable length inputs, the best way to validate strings (besides using a regular expression library) is to step through each character of the string and ensure it meets the validation criteria. That’s exactly what we’ll do here, or better, that’s what std::all_of
does for us.
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 |
#include <algorithm> // std::all_of #include <cctype> // std::isalpha, std::isspace #include <iostream> #include <string> #include <string_view> bool isValidName(std::string_view name) { return std::ranges::all_of(name, [](char ch) { return (std::isalpha(ch) || std::isspace(ch)); }); // Before C++20, without ranges // return std::all_of(name.begin(), name.end(), [](char ch) { // return (std::isalpha(ch) || std::isspace(ch)); // }); } int main() { std::string name{}; do { std::cout << "Enter your name: "; std::getline(std::cin, name); // get the entire line, including spaces } while (!isValidName(name)); std::cout << "Hello " << name << "!\n"; } |
Note that this code isn’t perfect: the user could say their name was “asf w jweo s di we ao” or some other bit of gibberish, or even worse, just a bunch of spaces. We could address this somewhat by refining our validation criteria to only accept strings that contain at least one character and at most one space.
Now let’s take a look at another example where we are going to ask the user to enter their phone number. Unlike a user’s name, which is variable-length and where the validation criteria are the same for every character, a phone number is a fixed length but the validation criteria differ depending on the position of the character. Consequently, we are going to take a different approach to validating our phone number input. In this case, we’re going to write a function that will check the user’s input against a predetermined template to see whether it matches. The template will work as follows:
A # will match any digit in the user input.
A @ will match any alphabetic character in the user input.
A _ will match any whitespace.
A ? will match anything.
Otherwise, the characters in the user input and the template must match exactly.
So, if we ask the function to match the template “(###) ###-####”, that means we expect the user to enter a ‘(‘ character, three numbers, a ‘)’ character, a space, three numbers, a dash, and four more numbers. If any of these things doesn’t match, the input will be rejected.
Here is the code:
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 44 45 46 47 48 49 50 51 52 53 54 55 |
#include <algorithm> // std::equal #include <cctype> // std::isdigit, std::isspace, std::isalpha #include <iostream> #include <map> #include <string> #include <string_view> bool inputMatches(std::string_view input, std::string_view pattern) { if (input.length() != pattern.length()) { return false; } // We have to use a C-style function pointer, because std::isdigit and friends // have overloads and would be ambiguous otherwise. static const std::map<char, int (*)(int)> validators{ { '#', &std::isdigit }, { '_', &std::isspace }, { '@', &std::isalpha }, { '?', [](int) { return 1; } } }; // Before C++20, use // return std::equal(input.begin(), input.end(), pattern.begin(), [](char ch, char mask) -> bool { // ... return std::ranges::equal(input, pattern, [](char ch, char mask) -> bool { if (auto found{ validators.find(mask) }; found != validators.end()) { // The pattern's current element was found in the validators. Call the // corresponding function. return (*found->second)(ch); } else { // The pattern's current element was not found in the validators. The // characters have to be an exact match. return (ch == mask); } }); } int main() { std::string phoneNumber{}; do { std::cout << "Enter a phone number (###) ###-####: "; std::getline(std::cin, phoneNumber); } while (!inputMatches(phoneNumber, "(###) ###-####")); std::cout << "You entered: " << phoneNumber << '\n'; } |
Using this function, we can force the user to match our specific format exactly. However, this function is still subject to several constraints: if #, @, _, and ? are valid characters in the user input, this function won’t work, because those symbols have been given special meanings. Also, unlike with regular expressions, there is no template symbol that means “a variable number of characters can be entered”. Thus, such a template could not be used to ensure the user enters two words separated by a whitespace, because it can not handle the fact that the words are of variable lengths. For such problems, the non-template approach is generally more appropriate.
Numeric validation
When dealing with numeric input, the obvious way to proceed is to use the extraction operator to extract input to a numeric type. By checking the failbit, we can then tell whether the user entered a number or not.
Let’s try this approach:
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 |
#include <iostream> #include <limits> int main() { int age{}; while (true) { std::cout << "Enter your age: "; std::cin >> age; if (std::cin.fail()) // no extraction took place { std::cin.clear(); // reset the state bits back to goodbit so we can use ignore() std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // clear out the bad input from the stream continue; // try again } if (age <= 0) // make sure age is positive continue; break; } std::cout << "You entered: " << age << '\n'; } |
If the user enters a number, cin.fail() will be false, and we will hit the break statement, exiting the loop. If the user enters input starting with a letter, cin.fail() will be true, and we will go into the conditional.
However, there’s one more case we haven’t tested for, and that’s when the user enters a string that starts with numbers but then contains letters (eg. “34abcd56”). In this case, the starting numbers (34) will be extracted into age, the remainder of the string (“abcd56”) will be left in the input stream, and the failbit will NOT be set. This causes two potential problems:
1) If you want this to be valid input, you now have garbage in your stream.
2) If you don’t want this to be valid input, it is not rejected (and you have garbage in your stream).
Let’s fix the first problem. This is easy:
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 |
#include <iostream> #include <limits> int main() { int age{}; while (true) { std::cout << "Enter your age: "; std::cin >> age; if (std::cin.fail()) // no extraction took place { std::cin.clear(); // reset the state bits back to goodbit so we can use ignore() std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // clear out the bad input from the stream continue; // try again } std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // clear out any additional input from the stream if (age <= 0) // make sure age is positive continue; break; } std::cout << "You entered: " << age << '\n'; } |
If you don’t want such input to be valid, we’ll have to do a little extra work. Fortunately, the previous solution gets us half way there. We can use the gcount() function to determine how many characters were ignored. If our input was valid, gcount() should return 1 (the newline character that was discarded). If it returns more than 1, the user entered something that wasn’t extracted properly, and we should ask them for new input. Here’s an example of this:
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 |
#include <iostream> #include <limits> int main() { int age{}; while (true) { std::cout << "Enter your age: "; std::cin >> age; if (std::cin.fail()) // no extraction took place { std::cin.clear(); // reset the state bits back to goodbit so we can use ignore() std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // clear out the bad input from the stream continue; // try again } std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // clear out any additional input from the stream if (std::cin.gcount() > 1) // if we cleared out more than one additional character { continue; // we'll consider this input to be invalid } if (age <= 0) // make sure age is positive { continue; } break; } std::cout << "You entered: " << age << '\n'; } |
Numeric validation as a string
The above example was quite a bit of work simply to get a simple value! Another way to process numeric input is to read it in as a string, then try to convert it to a numeric type. The following program makes use of that methodology:
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 44 |
#include <charconv> // std::from_chars #include <iostream> #include <optional> #include <string> #include <string_view> std::optional<int> extractAge(std::string_view age) { int result{}; auto end{ age.data() + age.length() }; // Try to parse an int from age if (std::from_chars(age.data(), end, result).ptr != end) { return {}; } if (result <= 0) // make sure age is positive { return {}; } return result; } int main() { int age{}; while (true) { std::cout << "Enter your age: "; std::string strAge{}; std::cin >> strAge; if (auto extracted{ extractAge(strAge) }) { age = *extracted; break; } } std::cout << "You entered: " << age << '\n'; } |
Whether this approach is more or less work than straight numeric extraction depends on your validation parameters and restrictions.
As you can see, doing input validation in C++ is a lot of work. Fortunately, many such tasks (eg. doing numeric validation as a string) can be easily turned into functions that can be reused in a wide variety of situations.
![]() |
![]() |
![]() |
I had a hard understand those functions. Can you explain more clearly what they do? This one (especially when it comes to std::ranges::equal):
[code]bool inputMatches(std::string_view input, std::string_view pattern)
{
if (input.length() != pattern.length())
{
return false;
}
// We have to use a C-style function pointer, because std::isdigit and friends
// have overloads and would be ambiguous otherwise.
static const std::map<char, int (*)(int)> validators{
{ '#', &std::isdigit },
{ '_', &std::isspace },
{ '@', &std::isalpha },
{ '?', [](int) { return 1; } }
};
// Before C++20, use
// return std::equal(input.begin(), input.end(), pattern.begin(), [](char ch, char mask) -> bool {
// ...
return std::ranges::equal(input, pattern, [](char ch, char mask) -> bool {
if (auto found{ validators.find(mask) }; found != validators.end())
{
// The pattern's current element was found in the validators. Call the
// corresponding function.
return (*found->second)(ch);
}
else
{
// The pattern's current element was not found in the validators. The
// characters have to be an exact match.
return (ch == mask);
}
});
}[\code]
And this function too:
[code]std::optional<int> extractAge(std::string_view age)
{
int result{};
auto end{ age.data() + age.length() };
// Try to parse an int from age
if (std::from_chars(age.data(), end, result).ptr != end)
{
return {};
}
if (result <= 0) // make sure age is positive
{
return {};
}
return result;
}[\code]
Thank You!!
`std::ranges::equal` (and `std::equal`) loops through both containers at the same time. For each pair of elements (1 element from `input` and 1 element from `pattern`), it calls the given comparison function. If the comparison function returned `true` for all elements, the containers are considered to be equal.
`std::from_chars` converts the given string to an integer. It returns `end` if all characters were parsed, ie. no error occurred.
this line in 3rd code block:
none of the string parameters were modified inside function, they all should be passed as a const reference (that's what we've been taught so far :)
That's right, thanks!
I gave the lesson quite an overhaul and tried using non-learncpp content starting in section "String validation". It makes the lessons more difficult, but should help readers to learn looking up what they need. A skill that is important, considering that we're close to the end of the tutorial. Let me know what you think of this idea :)
Hi! Should I use:
instead of gcount()? I found some inputs like "123 abc" is still successfully read. In such case, gcount() returns 0, not 1; whereas rdbuf()->in_avail() returns 5 because there are 5 remaining characters " abc\n" which causes the invalidation.
After skimming through the comments, there are many issues regarding the numbers used in this post. May I suggest creating named constants for these magic numbers? I feel you are on the right path using
though I do not know if this is the first time
was used.
I love the content, keep it up!
Can I use
instead of
?
No. `std::cin.gcount` returns the number of characters that have been extracted. That's not what you care about, you'd need the number of characters that are left in the input stream.
Passing `std::numeric_limits<std::streamsize>::max()` tells `std::cin.ignore` to disable the character count check and ignore everything up to a '\n'.
I feel like this tutorial was rushed. Like me, I see that a lot of people do not understand the string validation tutorial (first example of this current tutorial). Alex can you please make it more clear with input examples because it is not clearly explained as to how the programme is supposed to work.
Hi Alex, are you missing a main() routine in the first example?
It appears so. Fixed!
Alex,
Why is the number 1000 specifically chosen? I know that it means that 1000 characters in the stream will be ignored, but then why did the code samples of other chapters use the number 32767? On what basis are such numbers chosen?
Thanks
Hi Akshay!
32767 is the maximum value of a signed short int. I assume this was the first number that came to Alex' mind when thinking of a large number. 1000 is just another case of writing down the first big number that comes to mind.
None of those numbers has a special effect on std::istream::ignore.
Instead of the numbers used in the tutorials std::numeric_limits<std::streamsize>::max() should be used. According to the documentation passing this number will cause std::istream::ignore to wait indefinitely until delim is found.
Thank you for the nice explanation nascardriver!
32767 is the largest 2-byte signed integer, so it's the largest number that's guaranteed to work on all compilers.
The choice of 1000 in this lesson was arbitrary. I've updated it to 32767 for consistency.
Thank you so much Alex!
Alex,
Seems that the cctype header is not needed in any of these examples. They've all compiled and worked perfectly for me without it on macOS 10.12 using C++14 compiler settings.
it seems that the functions are in the string header.
http://en.cppreference.com/w/cpp/string/byte/isalnum indicates that the functions are in the cctype header. You (or your compiler) may be including this header either directly or indirectly (through inclusion of some other header that itself includes cctype).
Hi Alex!
Nice work, simple and clear.
At the last example "Numeric validation as a string" you declared a bool variable
that is not used.
It holds a value indicating whether the string is all digits. If not, then we continue back to the top of the loop and try again.
In the Function/Meaning table of Input Validation, why do they refer the argument as (int), when we're suppose to insert a char?
Whoever implemented those determined that they should take integer parameters -- not sure why. A char is an integer, so you can certainly pass in a char argument.
I have a very good friend called “asf w jweo s di we ao” - she won't be happy!
In the last approach, we actually don't even need to check if the user entered a negative number, because !isdigit(-) would return false and so would be treated as an invalid entry.
Actually that's only if your checking for negative age. In my program I allowed the age to be 0. But that's good to know if your only checking for explicitly negative numbers I guess.
Awesome tutorial, thank you so much!
One question and I'm sorry if it was mentioned before and I missed it: What exactly does
do?
See lesson 5.8 -- Break and continue.
Typo: missing "to" in the "badbit" row of the first table:
"(eg. the program tried read past the end of a file)" should be "(eg. the program tried to read past the end of a file)"
Fixed. Thanks!
"ios_base also provides a number of member functions in order to conveniently access these states:"
I think ios provides all the functions like fail(), good etc. (Everything in the first table). When I do this:
Program compiles fine.
Fixed the text to address this. Thanks for pointing this out. :)
Sorry for that messy formatting, I was in hurry.
I think i have much simpler approach - use some buffer. Via get() read input while checking conditions, if whole proces is ok write buffer to your variable, if not delete buffer,clear stream of garbage and start again. This way there is no garbage in your variable. With too long input you can just pass your buffer.
Should we code as
cin.ignore(1000, '\n');
rather than
cin.ignore(1000, 'n');
There is maybe one bug in example...it didn't compile in my env
// So we'll use stringstream to do that conversion
stringstream strStream;
Fix:
strstream strStream; // this works in my env
real wonderful tutorial !!!!!!!!!!!!!
anyone? please people i'm stuck..i feel like an android
well..i'm going crazy..i look at it again and again but i don't get anything..And guess im the only one who does not get anything about this template validation.?
we don't have any (? _ @) symbols in our (###) ###-#### template so how come we expect for our template's indexes will match with those symbols? we logically want strTemplate[nIndex] = # or strTemplate[nIndex] = ? etc.. to execute our statement under case label.. isn't that right? i dont know dude..what is this? what is this meaning:
we pass this string ---------> (###) ###-####
switch(strTemplate[nIndex])
{
case '@' : ---------------->(how could i expect my strTemplate[nIndex] will match a '@'symbol? we have no '@' symbol in our(###) ###-#### ???? and i need to match those two to execute statement under case label?? )
}
i know i miss something here and i can't see it.. i understood to whole c++ thing beggining to end except this template thing :D
please any help will be so appreciated.
I'm having the same issues understanding this as undo, can someone please explain?
-Much appreciated
We don’t have any (? _ @) symbols in our (###) ###-#### template so how come we expect for our template’s indexes will match with those symbols? we logically want strTemplate[nIndex] = # or strTemplate[nIndex] = ? etc.. to execute our statement under case label.. isn’t that right? i dont know dude..what is this? what is this meaning:
we pass this string ------> (###) ###-####
switch(strTemplate[nIndex])
{
case ‘@’ : ----------->(how could i expect my strTemplate[nIndex] will match a ‘@’symbol? we have no ‘@’ symbol in our(###) ###-#### ???? and i need to match those two to execute statement under case label?? )
}
In this case, because the template being passed in doesn't contain a @ (or ?), that switch case won't get executed.
Those are there just to show you how they could be implemented, in case you wanted to use a different template that did use those.
Is it because any sting is an array with a terminator at the end?
Is (strUserInput[nIndex] an 'Array' ?
If yes how can we use an array without first declaring it?
After the function that uses the template “(###) ###-####” the text says:
"if #, @, _, and ? are valid characters in the user input, this function won’t work, because those symbols have been given special meanings."
I don't get why it wouldn't work... Can somebody explain this? Has the "special meaning" been given by the code or does it originate in C++?
The special characters are missed by isdigit, isspace and isalpha. Since those three commands are being used to screen out bad input, any of those special characters would be treated as bad input. If they're valid, then the function won't work because it won't let those special characters through.
The only way I know how to test for them would be to make the conditionals that "return false" more stringent through the use of and's. For example, taking out
and replacing it with an added and, like so:
means the test conditional will not return "false" if a question mark is entered where a digit is supposed to be. That allows the user to substiture a question mark for a digit, but only for a digit.
Thanks a lot.
Really good article. Except I used
cin.ignore(std::numeric_limits::max(),'\n');
instead of
cin.ignore(1000, '\n');
I had a problem with two loops because I used clear and ignore at wrong places. So if 123abc was entered, somehow my app just kept silent until I pressed enter. Using your sample code for
"2) If you don’t want this to be valid input, it is not rejected (and you have garbage in your stream)."
fixed my problems and now my integer validation works reliably no matter when and how often I use it.
Thanks.
And how do you filter out F1 to F12 keys?
good program's.
i want some programs which give from user only digits.something like this.
if u have please send me on my e-mail.