3

When validating user input to check if the user has inputted correct values (i.e. letters for the strings, numbers for the integer), the below code can only detect when the strings have been incorrectly inputted by the user, not the integer. This means that as long as the user enters a number for the age integer, the code doesn't validate the input.

Here is my code.

#include <iostream>
#include <limits>
#include <string>
using namespace std;

string firstName, secondName, homeTown;
int age;

int main() {
    do {
        cout << "Please enter your full name, hometown and age (e.g. John Doe London 25).\n\n";
        cin >> firstName >> secondName >> homeTown >> age;
        if (cin.fail()) {
            cout << "\nInvalid input!\n\n";
            cin.clear();
            cin.ignore();
        }
        else {
            break;
        }
    } while (1);
    cout << "\nYour Name is: " << firstName << " " << secondName << "\n" << "Your Hometown is: " << homeTown << "\n" << "Your Age is: " << age << "\n";
    return 0;
}

Here's an example of how it outputs when everything is inputted correctly:

Please enter your full name, hometown and age (e.g. John Doe London 25).

John Doe London 25

Your Name is: John Doe
Your Hometown is: London
Your Age is: 25

Here's an example how it outputs when only the integer is inputted incorrectly:

Please enter your full name, hometown and age (e.g. John Doe London 25).

1 1 1 a

Invalid input!

Please enter your full name, hometown and age (e.g. John Doe London 25).

Here's the main issue, the output I get no matter what as long as the integer is inputted correctly, no matter how the strings are inputted:

Please enter your full name, hometown and age (e.g. John Doe London 25).

1 1 1 1

Your Name is: 1 1
Your Hometown is: 1
Your Age is: 1

To summarise, how exactly do I go about validating both an integer and multiple strings with an if statement?

6
  • 1
    Right? Why would the string "1" cause any problem with input? Perhaps after input you need to check if the firstName, secondName and homeTown variables are made up of nothing but digits. Perhaps using find_first_not_of()? E.g. if (firstName.find_first_not_of ("0123456789") != std::string::npos) { // firstName is all digits } Commented Oct 12, 2024 at 2:27
  • 1
    Also suggest std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); to ensure all extraneous characters are removed instead of just one. While fine for short example programs, make sure you visit Why is “using namespace std;” considered bad practice? Commented Oct 12, 2024 at 2:32
  • Appreciate your help David, I managed to use find_first_of() instead of find_first_not_of() to filter out numbers from the strings successfully. if (firstName.find_first_of("123456789") != std::string::npos || secondName.find_first_of("123456789") != std::string::npos || homeTown.find_first_of("123456789") != std::string::npos) { is the code I wrote. Only issue now is the opposite is happening, where the code isn't detecting the integer being inputted incorrectly (e.g. I enter "a" for age and it returns the input as successful instead of invalid) Commented Oct 12, 2024 at 3:04
  • 1
    Suppose you had a simpler case where you were entering just the hometown. How would you validate that? Don't introduce multiple fields until you have each working in isolation. Commented Oct 12, 2024 at 6:59
  • 1
    You might want to proofread your question and get rid of the contradictions. For example "the code can only detect when the strings have been incorrectly inputted by the user, not the integer." contradicts "as long as the user enters a number for the age integer, the code doesn't validate the input" (hence, does not detect when the strings are incorrect). Another example: 1 1 1 a is "an example [...] when only the integer is inputted incorrectly" which implies that 1 1 1 is correct for the strings, and yet your last example implies that 1 1 1 is not correct for the strings. Commented Oct 12, 2024 at 7:09

3 Answers 3

5

How exactly do I go about validating both an integer and multiple strings with an if statement?

When you need to validate the entries made by a user, you are usually better off inputting them one at a time. That way, you can check each one as it is entered, and ask the user to try again, when one of them is invalid.

I generally call a dedicated function for each input I want the user to enter. Function get_int, for instance, displays a prompt, and keeps looping until the user has entered a valid number. Entries must lie be between min and max, or else the user has to try again!

int get_int(
    std::string const& prompt,
    int const min,
    int const max)
{
    for (;;) {
        std::cout << prompt;
        if (int n{}; !(std::cin >> n)) {
            std::cout << "Invalid entry. Please reenter.\n\n";
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
        else if (n < min || n > max) {
            std::cout << "Invalid entry. Please reenter.\n"
                << "Entries must be between " << min << " and " << max << ".\n\n";
        }
        else {
            // The call to `ignore` ensures that this function "plays nice" 
            // with `std::getline`.
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << '\n';
            return n;
        }
    }
}

Here is a very similar function that inputs strings. It uses std::getline, which allows string entries to contain spaces. That way, you do not have to input first and last names separately (unless you want to). Entries are not allowed to be blank.

std::string get_non_empty_string(std::string const& prompt)
{
    for (;;) {
        std::cout << prompt;
        if (std::string s; !(std::getline(std::cin, s))) {
            throw std::runtime_error{ "get_non_empty_string: `std::getline` failed" };
        }
        else if (s.empty()) {
            std::cout << "Invalid entry. Please reenter.\n"
                << "Entries cannot be blank.\n\n";
        }
        else {
            std::cout << '\n';
            return s;
        }
    }
}

A complete program

Using the two functions above allows you to simplify function main. Note that I have dispensed with using namespace std;. Most professionals stay away from it. I have also eliminated the global variables. They may be okay in a small program such as this one, but they can be nasty in larger programs, where mistakes can be difficult to track down.

// main.cpp
#include <iostream>
#include <limits>
#include <stdexcept>
#include <string>

//==================================================================
// get_int
//==================================================================
int get_int(
    std::string const& prompt,
    int const min,
    int const max)
{
    for (;;) {
        std::cout << prompt;
        if (int n{}; !(std::cin >> n)) {
            std::cout << "Invalid entry. Please reenter.\n\n";
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
        else if (n < min || n > max) {
            std::cout << "Invalid entry. Please reenter.\n"
                << "Entries must be between " << min << " and " << max << ".\n\n";
        }
        else {
            // The call to `ignore` ensures that this function "plays nice" 
            // with `std::getline`.
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            std::cout << '\n';
            return n;
        }
    }
}
//==================================================================
// get_non_empty_string
//==================================================================
std::string get_non_empty_string(std::string const& prompt)
{
    for (;;) {
        std::cout << prompt;
        if (std::string s; !(std::getline(std::cin, s))) {
            throw std::runtime_error{ "get_non_empty_string: `std::getline` failed" };
        }
        else if (s.empty()) {
            std::cout << "Invalid entry. Please reenter.\n"
                << "Entries cannot be blank.\n\n";
        }
        else {
            std::cout << '\n';
            return s;
        }
    }
}
//==================================================================
// main
//==================================================================
int main() {
    std::cout << "Please tell us about yourself.\n\n";
    std::string full_name = get_non_empty_string("Full name: ");
    std::string hometown = get_non_empty_string("Hometown: ");
    int age = get_int("Age: ", 0, 125);
    std::cout 
        << "\nYour Name is: " << full_name<< "\n" 
        << "Your Hometown is: " << hometown << "\n" 
        << "Your Age is: " << age << "\n";
    return 0;
}
// end file: main.cpp

Sample output

No errors were made during the first run.

Please tell us about yourself.

Full name: Joe Dokes

Hometown: London

Age: 25


Your Name is: Joe Dokes
Your Hometown is: London
Your Age is: 25

The second time around, mistakes abounded. This run shows the problem with inputting full name, rather than separate first and last names. There was no way to detect that the last name was omitted.

Please tell us about yourself.

Full name:
Invalid entry. Please reenter.
Entries cannot be blank.

Full name: Joe

Hometown:
Invalid entry. Please reenter.
Entries cannot be blank.

Hometown: London, England

Age: xxx
Invalid entry. Please reenter.

Age: 1000
Invalid entry. Please reenter.
Entries must be between 0 and 125.

Age: 25


Your Name is: Joe
Your Hometown is: London, England
Your Age is: 25
Sign up to request clarification or add additional context in comments.

1 Comment

Awesome. I'll definitely rely on this method in the future since it actually detects if the user doesn't input anything. I would've made sure each variable was prompted for user input on different lines like you have, but the assignment I'm working on says I need to keep std::cout's to a minimum. Cheers!
2

Just to continue from my comments, using std::getline() and a std::stringstream will allow individual checks on all inputs and simplifies the check on conversion to int as all you need to do is check whether the conversion succeeds or fails. By using getline() to consume the entire line of input, you are freed from ignoring characters that remain in stdin.

From my comments:

  • As you have figured out, any input that you read as std::string will be taken as valid input. "1" is just as valid as any other string. To handle that circumstance you need to check that each input conforms to the needs of your programs. You have indicated that firstName, secondName and homeTown should not be all digits, so you should check that each contain characters other than digits. (.find_first_not_of() fits nicely with a string of "0123456789" as the str parameter. std::basic_string::find_first_not_of())
  • If you need to ensure the input stream is clear of characters, you should clear the stream with, e.g. std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); instead of just std::cin.ignore() which removes a single character.
  • Also as mentioned, while using namespace std is fine for short example programs, make sure you take a look at Why is “using namespace std;” considered bad practice? to understand the considerations that go along with it.

I like the way @tbxfreeware created get_non_empty_string() to validate non-empty input. This will save a bit of code duplication if using std::cin >>, but it is more or less a wash with getline() as you only have one input to check. (you do check the others later).

Also note, writing code isn't like writing a poem. A little duplication doesn't hurt. Readable code and code that is maintainable by the next guy (or gal) is the goal. This is an iterative process. Get your code working, then revise and factor your code into functions, classes, structs, etc. as makes sense. Nobody writes perfect code on the first attempt.

The standard approach to a read-loop is to loop continually, and validate each input, if any input fails, empty the input stream and loop again. Only if all inputs pass your input validations do you break the read-loop with your input ready for use. (you also need to allow the user to cancel input). There are many ways to do this and you can tailor how you do it to meet the input you need.

Putting the pieces together from the comments and changing the != to == on the .find_first_not_of() calls, you could do something like this:

#include <iostream>
#include <sstream>
#include <string>

constexpr std::string digits {"0123456789"};

int main() {
  /* initialize all variables */
  std::string firstName{}, secondName{}, homeTown{};
  int age = 0;
  
  do {
    std::string s{};    /* string to read entire line */
    
    std::cout <<  "\nPlease enter your full name, homeTown and age "
                  "(e.g. John Doe London 25).\n\n";
    /* validate read of line */
    if (!getline (std::cin, s)) {
      std::cerr << "  warning: user canceled input with manual EOF.\n";
      return 0;     /* not an error, just return 0 */
    }
    
    std::stringstream ss (s);   /* create stringstream from line */
    
    if (!(ss >> firstName)) {   /* read firstName from stringstream */
      std::cerr << "  error: no input for firstName.\n";
      continue;
    }
    /* validate not all digits */
    if (firstName.find_first_not_of (digits) == std::string::npos) {
      std::cerr << "  error: firstName all digits.\n";
      continue;
    }
    
    if (!(ss >> secondName)) {  /* read secondName from stringstream */
      std::cerr << "  error: no input for secondName.\n";
      continue;
    }
    /* validate not all digits */
    if (secondName.find_first_not_of (digits) == std::string::npos) {
      std::cerr << "  error: secondName all digits.\n";
      continue;
    }
    
    if (!(ss >> homeTown)) {    /* read homeTown from stringstream */
      std::cerr << "  error: no input for homeTown.\n";
      continue;
    }
    /* validate not all digits */
    if (homeTown.find_first_not_of (digits) == std::string::npos) {
      std::cerr << "  error: homeTown all digits.\n";
      continue;
    }
    
    if (!(ss >> age) ) {   /* check conversion to integer */
      std::cerr << "  error: no or invalid integer input for age.\n";
      continue;
    }
    break;    /* passed all input tests - good input, break input loop */
    
  } while (1);
  
  std::cout << "\nYour Name is: " << firstName << " " << secondName << "\n" 
            << "Your Hometown is: " << homeTown << "\n" 
            << "Your Age is: " << age << "\n";
}

Note: using .find_first_not_of() allows you to check for inputs that are all digits not that just contain a digit -- completely up to you what you need.

Compile String with Full-Warnings Enabled

g++ -Wall -Wextra -pedantic -Wshadow -march=native -std=c++23 -O3 -o bin/input-strstrstrint input-strstrstrint.cpp

Example Use Output

Checking the inputs you can do something similar to:

$ ./bin/input-strstrstrint

Please enter your full name, homeTown and age (e.g. John Doe London 25).

1234
  error: firstName all digits.

Please enter your full name, homeTown and age (e.g. John Doe London 25).

Mickey 1234
  error: secondName all digits.

Please enter your full name, homeTown and age (e.g. John Doe London 25).

Mickey Mouse
  error: no input for homeTown.

Please enter your full name, homeTown and age (e.g. John Doe London 25).

Mickey Mouse 1234
  error: homeTown all digits.

Please enter your full name, homeTown and age (e.g. John Doe London 25).

Mickey Mouse Disney
  error: no or invalid integer input for age.

Please enter your full name, homeTown and age (e.g. John Doe London 25).

Mickey Mouse Disney Old
  error: no or invalid integer input for age.

Please enter your full name, homeTown and age (e.g. John Doe London 25).

Mickey Mouse Disney 102

Your Name is: Mickey Mouse
Your Hometown is: Disney
Your Age is: 102

Note: if a user generates a manual EOF for the input with Ctrl + d (Ctrl +z on windows), the program returns 0 (it's not an error for the user to cancel input - so just handle it however you need). You can also move the input to a separate function and pass a reference to each of the variables you need filled as parameters -- up to you.

You have many good answers to your question. Draw learning from each.

2 Comments

"Just to continue from my comments" - you should move/summarize those details in your answer. Don't make people have to scroll up and find your comments.
Good point, will do.
1

Figured it out thanks to Dave C Rankin.

Here is the code:

#include <iostream>
#include <limits>
#include <string>

std::string firstName, secondName, homeTown;
int age;

int main() {
    do {
        std::cout << "Please enter your full name, hometown and age (e.g. John Doe London 25).\n\n";
        std::cin >> firstName >> secondName >> homeTown >> age;
        if (firstName.find_first_of("0123456789") != std::string::npos || secondName.find_first_of("0123456789") != std::string::npos || homeTown.find_first_of("0123456789") != std::string::npos) {
            std::cout << "\nInvalid input!\n\n";
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }
        else {
            if (std::cin.fail()) {
                std::cout << "\nInvalid input!\n\n";
                std::cin.clear();
                std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            }
            else {
                break;
            }
        }
    } while (1);
    std::cout << "\nYour Name is: " << firstName << " " << secondName << "\n" << "Your Hometown is: " << homeTown << "\n" << "Your Age is: " << age << "\n";
    return 0;
}

Instead of using a single if statement, I split it between two so that firstName, secondName and homeTown could be filtered seperately from age.

This is what I used to for validating the strings: if (firstName.find_first_of("0123456789") != std::string::npos || secondName.find_first_of("0123456789") != std::string::npos || homeTown.find_first_of("0123456789") != std::string::npos) {

find_first_of() allows the code to search for any numbers being inputted for the strings. If any are detected it returns the error message successfully. I used firstName, secondName and homeTown all in one if statement using the ||/OR operator.

for age, I simply implemented a if (std::cin.fail()) to check automatically for any mis-inputs, as it worked in the past just fine.

I also made some miscellaneous changes to the code such as removing using namespace std; that aren't especially important, however, the rest of the code remains unchanged.

Here are some examples of outputs now:

If the user correctly inputs the values:

Please enter your full name, hometown and age (e.g. John Doe London 25).

John Doe London 25

Your Name is: John Doe
Your Hometown is: London
Your Age is: 25

If the user incorrectly inputs age:

Please enter your full name, hometown and age (e.g. John Doe London 25).

a a a a

Invalid input!

Please enter your full name, hometown and age (e.g. John Doe London 25).

If the user incorrectly inputs firstName, secondName and/or homeTown

Please enter your full name, hometown and age (e.g. John Doe London 25).

1 1 1 1

Invalid input!

Please enter your full name, hometown and age (e.g. John Doe London 25).

If the user inputs none of the variables correctly:

Please enter your full name, hometown and age (e.g. John Doe London 25).

1 1 1 a

Invalid input!

Please enter your full name, hometown and age (e.g. John Doe London 25).

Sweet!

1 Comment

I up-voted this just to give the new guy some encouragement. He paid attention to the comments that were made in response to his question, and he worked out an answer that was good enough for him!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.