How-To: Get valid integer input in C++ (a stupidly long solution to a stupidly simple problem)

Update: See the comments for a far, far more elegent and robust solution courtesy of the mighty shetboy!


One of the assessments I’ve got my diploma class doing at the moment is to fix all the bugs in some deliberately shonky C++ code. The code just asks for a name (string) and an age in years (int), and it then multiplies the age in years by 365 to give a rough indication of how many days you’ve been alive. So for example if I enter Al and 34 it comes back with:

Hi, Al! You’ve been alive for roughly 12410 days!

The problems come thick and fast though if you want to verify that the age in years entered is really an integer value. You CAN do so like this:

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. int main()
  5. {
  6.     int yearsOld       = 0;
  7.     bool gotValidInput = false;
  8.  
  9.     do
  10.     {
  11.         cout << "Please enter your age in years: ";
  12.         cin >> yearsOld;
  13.  
  14.         if (cin.fail())
  15.         {
  16.             cin.clear();
  17.             cin.ignore();
  18.             cout << "Invalid entry! Whole numbers only, dawg!" << endl;
  19.         }
  20.         else
  21.         {
  22.             // We got some valid input! Set the flag so we can leave the loop!
  23.             gotValidInput = true;
  24.         }
  25.  
  26.     } while ( gotValidInput == false );
  27.  
  28.     cout << "Valid value is: " << yearsOld << endl;
  29.  
  30.     return 0;
  31. }

But you then end up with output like this where each character fails individually:

Please enter your age in years: Mooo...
Invalid entry! Whole numbers only, dawg!
Please enter your age in years: Invalid entry! Whole numbers only, dawg!
Please enter your age in years: Invalid entry! Whole numbers only, dawg!
Please enter your age in years: Invalid entry! Whole numbers only, dawg!
Please enter your age in years: Invalid entry! Whole numbers only, dawg!
Please enter your age in years: Invalid entry! Whole numbers only, dawg!
Please enter your age in years: Invalid entry! Whole numbers only, dawg!
Please enter your age in years:

Obviously that’s no good, so I researched a bit and came up with a solution but –DAMN– it doesn’t feel like the right one… Check out all this clumsiness:

  1. #include <iostream>
  2. #include <sstream>
  3.  
  4. using namespace std;
  5.  
  6. int getValidInteger(string prompt)
  7. {
  8.     bool debug = true;                        // Toggle for verbosity
  9.  
  10.     bool   valid;                             // Our exit flag - we only get out of the do..while loop when this is true
  11.     bool   mixedOperators;                    // Did the user enter a mixture of + and - operators (i.e. -5+3)
  12.     bool   multipleOperators;                 // Did the user enter multiple + or - operators      (i.e. -5-3 or +5+3)
  13.     int    userInt = 0;                       // Integer value we finally return after validation
  14.     string userInput;                         // User input is taken as a string before being wrangled...
  15.     int    foundInvalidInputLocation;         // Character location of any invalid input
  16.  
  17.     string validCharacters  = "+-0123456789"; // All the valid characters we're willing to accept as valid integer input
  18.  
  19.     do
  20.     {
  21.         // Set our initial flags
  22.         valid                     = false;
  23.         mixedOperators            = false;
  24.         multipleOperators         = false;
  25.         foundInvalidInputLocation = 0;
  26.  
  27.         // Display the prompt and get the user input as a string
  28.         cout << endl << prompt << endl;
  29.         getline(cin, userInput);
  30.  
  31.         // Convert the user input string into a input stringstream then...
  32.         istringstream iss(userInput);
  33.  
  34.         // ...try to convert the istringstream into an integer.
  35.         if (iss >> userInt)
  36.         {
  37.             if (debug)
  38.             {
  39.                 cout << "This already is or can be made into a valid integer!" << endl;
  40.             }
  41.  
  42.             // Now cast our input string-stream back to a string and...
  43.             string test = iss.str();
  44.  
  45.             // ...test to see if we found any illegal characters (because "123Foo!" -WILL- convert to the int 123, but we want legit input!
  46.             foundInvalidInputLocation = test.find_first_not_of(validCharacters);
  47.  
  48.             if (debug)
  49.             {
  50.                 cout << "Value of foundInvalidInputLocation is: " << foundInvalidInputLocation << endl;
  51.             }
  52.  
  53.             // Check whether we found an character which cannot be part of a valid int (-1 indicates no invalid character found)
  54.             if (foundInvalidInputLocation != -1)
  55.             {
  56.                 if (debug)
  57.                 {
  58.                     cout << "Although we can cast the provided input to an int..." << endl;
  59.                     cout << "...the original input contained at least one invalid character at location: " << foundInvalidInputLocation << endl;
  60.                 }
  61.             }
  62.             else // No non-digit characters found? Then we were likely given a legitimate int - but we still need to do more testing to be sure!
  63.             {
  64.                 // Find the locations of the first and last plus and minus operators in our converted-from-iss string
  65.                 int firstMinusOperatorLoc = test.find_first_of("-");
  66.                 int lastMinusOperatorLoc  = test.find_last_of("-");
  67.                 int firstPlusOperatorLoc  = test.find_first_of("+");
  68.                 int lastPlusOperatorLoc   = test.find_last_of("+");
  69.  
  70.                 if (debug)
  71.                 {
  72.                     // Wax lyrical about multiple negative operators if detected...
  73.                     cout << "First and last minus signs are at: " << firstMinusOperatorLoc << " and " << lastMinusOperatorLoc << " Match?: ";
  74.                     firstMinusOperatorLoc == lastMinusOperatorLoc? cout << "Yes! This is good!" << endl : cout << "No! Multiple minus operators detected!" << endl;
  75.  
  76.                     // Wax lyrical about multiple positive operators if detected...
  77.                     cout << "First and last plus signs are at: " << firstPlusOperatorLoc << " and " << lastPlusOperatorLoc << " Match?: ";
  78.                     firstPlusOperatorLoc == lastPlusOperatorLoc? cout << "Yes! This is good!" << endl : cout << "No! Multiple plus operators detected!" << endl;
  79.                 }
  80.  
  81.                 // Got a mixture of both positive and negative operators in the input? Then it's not a valid int!
  82.                 if ( (firstMinusOperatorLoc != -1) && (firstPlusOperatorLoc != -1) )
  83.                 {
  84.                     if (debug)
  85.                     {
  86.                         cout << "Mixed + and - operators detected, so flagging this as invalid input!" << endl;
  87.                     }
  88.  
  89.                     mixedOperators = true;
  90.                 }
  91.                 else // No mixed operators? Then we can continue to check for multiple operators...
  92.                 {
  93.                     if (debug)
  94.                     {
  95.                         cout << "No mixed operators - continuing... " << endl;
  96.                     }
  97.  
  98.                     // Got multiple negative or positive operators in the input? Then it's not a valid int!
  99.                     if ( (firstMinusOperatorLoc != lastMinusOperatorLoc) || (firstPlusOperatorLoc != lastPlusOperatorLoc) )
  100.                     {
  101.                         if (debug)
  102.                         {
  103.                             cout << "Multiple + or - operators detected, so flagging this as invalid input!" << endl;
  104.                         }
  105.  
  106.                         multipleOperators = true;
  107.                     }
  108.                     else
  109.                     {
  110.                         if (debug)
  111.                         {
  112.                             cout << "No multiple operators - continuing... " << endl;
  113.                         }
  114.                     }
  115.  
  116.                 } // End of if mixed operator check else-block
  117.  
  118.                 // If we've finally got here and we don't have mixed or multiple operators - then FINALLY we know we have a valid int...
  119.                 if ( (mixedOperators == false) && (multipleOperators == false) )
  120.                 {
  121.                     if (debug) { cout << "Got a truly valid int! Hurrah!" << endl; }
  122.  
  123.                     // ...so set our exit flag to get out of the do..while loop
  124.                     valid = true;
  125.                 }
  126.  
  127.             } // End of if (foundInvalidInputLocation != -1) else section
  128.  
  129.         } // End of if (iss >> userInt) section
  130.         else
  131.         {
  132.             cout << "That's not a valid integer value!" << endl;
  133.         }
  134.  
  135.     } while (valid == false);
  136.  
  137.     // Because we got out of the loop, we must have a valid integer, which we can now return!
  138.     return userInt;
  139. }
  140.  
  141.  
  142. int main()
  143. {
  144.     int x = getValidInteger("Please enter a valid integer: ");
  145.  
  146.     cout << endl << "The value of our valid integer is: " << x << endl;
  147.  
  148.     //system("pause");
  149.     return 0;
  150. }

That works perfectly well as far as I’m concerned (remember – I’m not trying to validate the int to be within a given range or anything – I just want a valid int!) but something is surely wrong here… this should be a one-liner, or maybe three or four lines at the absolute most, not 140 lines or so…

Anyone got any suggestions on how to do this the right way? (In C++ not C, so no atoi or C-strings, and no boost library please! Just “standard” C++!).

7 thoughts on “How-To: Get valid integer input in C++ (a stupidly long solution to a stupidly simple problem)”

  1. Gosh, that is indeed clumsy. Can you use pre-made objects like in Java and then catch the exception? This will also work for -ve numbers, etc.

    public int stringToInt(String enteredValue) throws NumberFormatException {
    	return Integer.parseInt(enteredValue);
    }
     
    public float stringToFloat(String enteredValue) throws NumberFormatException {
    	return Float.parseFloat(enteredValue);
    }
  2. Just spotted on this page

    while (cout << "Enter Quantity: " && !(cin >> Quantity)  || (Quantity < 1) || cin.peek() != '\n')
    {    
    	cout << "Quantity must be  whole positive number!" << endl;    
    	cin.clear();
    	cin.ignore();
    	cin.ignore(numeric_limits<streamsize>::max(), '\n');
    }

    Apologies if I messed up the markup code :)

    (r3dux: All good! It’s “cpp” for C++, list of valid languages for highlighting plus examples can be found here)

    1. That is absolutely perfect! You legend!!!

      With the code above tho the guy messed up and added two cin.ignore() statements so you need to push enter after each failed entry, I just did a quick mod to fix and added a top validation limit, which gives us this:

      #include <iostream>
      #include <limits>
      using namespace std;
       
      int main()
      {
          const int MIN = 1;
          const int MAX = 100;
          int number;
       
          while ( (cout << "Enter a valid number: " && !(cin >> number) )  || (number < MIN) || (number > MAX) || cin.peek() != '\n')
          {
              cout << "You must input a whole number between 1 and 100!" << endl;
              cin.clear();
              cin.ignore(numeric_limits<streamsize>::max(), '\n');
          }
       
          cout << "Successfully got the valid integer value: " << number << endl;
       
          return 0;
      }

      That works beautifully, and saves 130 lines of clumsiness! I think you’ve unlocked an achievement!

      King of the Code achievement

      =D

      1. May as well refactor into a function:

        #include <iostream>
        #include <limits>
        using namespace std;
         
        int getValidInt(int min, int max)
        {
        	int number;
         
        	while ( (cout << "Enter a valid number: " && !(cin >> number) )  || (number < min) || (number > max) || cin.peek() != '\n')
        	{
        		cout << "You must input a whole number between " << min << " and " << max << endl;
        		cin.clear();
        		cin.ignore(numeric_limits<streamsize>::max(), '\n');
        	}
         
        	return number;
        }
         
        int main()
        {
        	int x = getValidInt(1, 100);
         
        	cout << "Successfully got the valid integer value: " << x << endl;
         
        	return 0;
        }

        Still lovin’ it =D

        1. That’s good :)

          However it is preferable when developing functions like this to not output anything, they should be like submarines… silent running.

          The CALLING function should deal with IO, the LIBRARY function should deal with parsing the integer or whatever needs to be done. Exceptions should be used when things go wrong, then the calling function can do what is required.

          After all, the calling function may need to do different things depending on its context. The library function will have no idea what that could possibly be as it will be impossible to predict all scenarios.

          You will then be able to use the function for command line utilities, Windows Framework applications, QT, etc, etc.

  3. Great advice – which I’ve tried to take on board – but I feel like I’m losing the plot with all this =/

    48e-Get-Valid-Int-(Reusable-Function-Version).7z
    48f-Get-Valid-Int-(Unit-Test-Version).7z

    I know I should really have multiple projects in the same workspace to perform unit tests but I’m getting towards the end of it…

    If you have the time to take a quick swiz (they’re both Code::Blocks projects) and let me know if I’ve got the right end of the stick or if I’ve got the wrong stick entirely (which is the feeling I’ve got) then it would be greatly appreciated – no worries if not tho, I know you’re a busy bloke!

    /me hangs up his coding hat for the day…

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.