How-To: Get valid integer input in C++ (a stupidly long solution to a stupidly simple problem)
r3dux | November 15, 2011Update: 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 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 | #include <iostream> using namespace std; int main() { int yearsOld = 0; bool gotValidInput = false; do { cout << "Please enter your age in years: "; cin >> yearsOld; if (cin.fail()) { cin.clear(); cin.ignore(); cout << "Invalid entry! Whole numbers only, dawg!" << endl; } else { // We got some valid input! Set the flag so we can leave the loop! gotValidInput = true; } } while ( gotValidInput == false ); cout << "Valid value is: " << yearsOld << endl; return 0; } |
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 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 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | #include <iostream> #include <sstream> using namespace std; int getValidInteger(string prompt) { bool debug = true; // Toggle for verbosity bool valid; // Our exit flag - we only get out of the do..while loop when this is true bool mixedOperators; // Did the user enter a mixture of + and - operators (i.e. -5+3) bool multipleOperators; // Did the user enter multiple + or - operators (i.e. -5-3 or +5+3) int userInt = 0; // Integer value we finally return after validation string userInput; // User input is taken as a string before being wrangled... int foundInvalidInputLocation; // Character location of any invalid input string validCharacters = "+-0123456789"; // All the valid characters we're willing to accept as valid integer input do { // Set our initial flags valid = false; mixedOperators = false; multipleOperators = false; foundInvalidInputLocation = 0; // Display the prompt and get the user input as a string cout << endl << prompt << endl; getline(cin, userInput); // Convert the user input string into a input stringstream then... istringstream iss(userInput); // ...try to convert the istringstream into an integer. if (iss >> userInt) { if (debug) { cout << "This already is or can be made into a valid integer!" << endl; } // Now cast our input string-stream back to a string and... string test = iss.str(); // ...test to see if we found any illegal characters (because "123Foo!" -WILL- convert to the int 123, but we want legit input! foundInvalidInputLocation = test.find_first_not_of(validCharacters); if (debug) { cout << "Value of foundInvalidInputLocation is: " << foundInvalidInputLocation << endl; } // Check whether we found an character which cannot be part of a valid int (-1 indicates no invalid character found) if (foundInvalidInputLocation != -1) { if (debug) { cout << "Although we can cast the provided input to an int..." << endl; cout << "...the original input contained at least one invalid character at location: " << foundInvalidInputLocation << endl; } } 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! { // Find the locations of the first and last plus and minus operators in our converted-from-iss string int firstMinusOperatorLoc = test.find_first_of("-"); int lastMinusOperatorLoc = test.find_last_of("-"); int firstPlusOperatorLoc = test.find_first_of("+"); int lastPlusOperatorLoc = test.find_last_of("+"); if (debug) { // Wax lyrical about multiple negative operators if detected... cout << "First and last minus signs are at: " << firstMinusOperatorLoc << " and " << lastMinusOperatorLoc << " Match?: "; firstMinusOperatorLoc == lastMinusOperatorLoc? cout << "Yes! This is good!" << endl : cout << "No! Multiple minus operators detected!" << endl; // Wax lyrical about multiple positive operators if detected... cout << "First and last plus signs are at: " << firstPlusOperatorLoc << " and " << lastPlusOperatorLoc << " Match?: "; firstPlusOperatorLoc == lastPlusOperatorLoc? cout << "Yes! This is good!" << endl : cout << "No! Multiple plus operators detected!" << endl; } // Got a mixture of both positive and negative operators in the input? Then it's not a valid int! if ( (firstMinusOperatorLoc != -1) && (firstPlusOperatorLoc != -1) ) { if (debug) { cout << "Mixed + and - operators detected, so flagging this as invalid input!" << endl; } mixedOperators = true; } else // No mixed operators? Then we can continue to check for multiple operators... { if (debug) { cout << "No mixed operators - continuing... " << endl; } // Got multiple negative or positive operators in the input? Then it's not a valid int! if ( (firstMinusOperatorLoc != lastMinusOperatorLoc) || (firstPlusOperatorLoc != lastPlusOperatorLoc) ) { if (debug) { cout << "Multiple + or - operators detected, so flagging this as invalid input!" << endl; } multipleOperators = true; } else { if (debug) { cout << "No multiple operators - continuing... " << endl; } } } // End of if mixed operator check else-block // If we've finally got here and we don't have mixed or multiple operators - then FINALLY we know we have a valid int... if ( (mixedOperators == false) && (multipleOperators == false) ) { if (debug) { cout << "Got a truly valid int! Hurrah!" << endl; } // ...so set our exit flag to get out of the do..while loop valid = true; } } // End of if (foundInvalidInputLocation != -1) else section } // End of if (iss >> userInt) section else { cout << "That's not a valid integer value!" << endl; } } while (valid == false); // Because we got out of the loop, we must have a valid integer, which we can now return! return userInt; } int main() { int x = getValidInteger("Please enter a valid integer: "); cout << endl << "The value of our valid integer is: " << x << endl; //system("pause"); return 0; } |
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++!).












