23.5 — Stream states and input validation

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:

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. The C++ standard library has a regular expression library as well. Regular expressions are slow compared to manual string validation, they should only be used if performance (compile-time and run-time) is of no concern or manual validation is too cumbersome.

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.

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:

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:

If the user enters a number, will be false, and we will hit the break statement, exiting the loop. If the user enters input starting with a letter, 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:

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:

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:

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.

23.6 -- Basic file I/O
23.4 -- Stream classes for strings

48 comments to 23.5 — Stream states and input validation

  • TheNightKnight

    "C++ does not have built-in regular expression support (it’s supposedly coming with the next revision of C++)"
    Wasn't it added in C++11?! <regex>

  • giang

    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);
          // The pattern's current element was not found in the validators. The
          // characters have to be an exact match.
          return (ch == mask);

    And this function too:

    [code]std::optional<int> extractAge(std::string_view age)
      int result{};
      auto end{ + age.length() };

      // Try to parse an int from age
      if (std::from_chars(, end, result).ptr != end)
        return {};

      if (result <= 0) // make sure age is positive
        return {};

      return result;

    Thank You!!

    • nascardriver

      `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.

  • saj

    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 :)

    • nascardriver

      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 :)

  • vie_man

    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!

  • Evrik

    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'.

  • Maison

    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?

  • Akshay Chavan


    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?


    • nascardriver

      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.

    • Alex

      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.

  • Lamont Peterson


    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.

    • Alex 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).

  • Aires Justo

    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.

    • Alex

      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.

  • Kattencrack Kledge

    In the Function/Meaning table of Input Validation, why do they refer the argument as (int), when we're suppose to insert a char?

    • Alex

      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.

  • Darren

    I have a very good friend called “asf w jweo s di we ao” - she won't be happy!

  • SJ

    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.

  • Reggi

    Awesome tutorial, thank you so much!

    One question and I'm sorry if it was mentioned before and I missed it: What exactly does


  • Matt

    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)"

  • Devashish

    "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.

  • Jiri

    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.

  • LW

    Should we code as
    cin.ignore(1000, '\n');
    rather than
    cin.ignore(1000, 'n');

  • Ville K

    There is maybe one bug in didn't compile in my env

    // So we'll use stringstream to do that conversion
    stringstream strStream;
    strstream strStream; // this works in my env

  • real wonderful tutorial !!!!!!!!!!!!!

  • undo

    anyone? please people i'm stuck..i feel like an android

  • undo

    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 ---------> (###) ###-####

    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 ------> (###) ###-####

      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?? )

      • Alex

        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.

  • Ravi Gautam

    Is it because any sting is an array with a terminator at the end?

  • Ravi Gautam

    Is (strUserInput[nIndex] an 'Array' ?

    If yes how can we use an array without first declaring it?

  • bla

    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++?

    • D.M. Ryan

      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.

  • Martin

    Thanks a lot.
    Really good article. Except I used
    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.


  • Violator

    And how do you filter out F1 to F12 keys?

  • sagar

    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.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]