2

This function keeps getting called in another function inside a while-loop while valid_office_num is false. The problem is that if the input begins with a digit but is followed by other invalid characters (e.g. 5t) it takes the digit part and accepts that as a valid input. I want it to consider the whole input and reject it so it can ask for another one. I thought I could use getline() but then I cannot use cin.fail(). How could I implement this behavior?

I forgot to mention I am very new to C++, I have only learnt the basics so far.

(To be clear the desired behavior is to reject anything that contains anything other than digits. This is not an integer range check question. If it is NOT an integer, discard it and request another one)

//Function to read a valid office number, entered by user
int read_office_num()
{
    //Declaration of a local variable
    int office_num;

    //Read input
    cin >> office_num;

    //Check if input was valid
    if (cin.fail())
    {
        //Print error message
        cout << "\nInvalid office number, it should only consist of digits!! Enter another:\n";
        //Clear error flags
        cin.clear();
        //Ignore any whitespace left on input stream by cin
        cin.ignore(256, '\n');
    }
    else
    {
        //Office number entered is valid
        valid_office_num = true;
    }

    return office_num;
}
7
  • 1
    You can add header file "limits.h" and go for check that office_num should lie between INT_MAX and INT_MIN. Commented Apr 15, 2015 at 11:38
  • possible duplicate of integer input validation, how? Commented Apr 15, 2015 at 12:03
  • 1
    @NathanOliver I don't think that's a valid duplicate. That question is concerned with validating the integer read. This question is interested in validating the entire input. Though it does seem surprising to me that this has not been asked yet. Commented Apr 15, 2015 at 12:06
  • 1
    if (!(std::cin >> n >> std::ws) || !std::cin.eof())) if your entire input is just one number. This will gobble up leading and trailing whitespace, so if that doesn't consume the entire string, you have an error. The approach can be combined with getline and istringstream to work on a per-line basis. Commented Apr 15, 2015 at 12:11
  • 1
    @JonathanMee I put it as a dube because the accepted answer does have a good detail of how to make sure you are getting good numeric data and also that it is in the range that you want. Commented Apr 15, 2015 at 12:21

6 Answers 6

2

From what I gather you want the whole line to be read as a number and fail otherwise?

Well, you can use std::getline(), but you have to follow the algorithm below (I will leave the implementation to you..)

  1. use std::getline(cin, str) to read a line, and if this returns true
  2. use std::stoi(str, &pos) to convert to integer and get the position of the last integer
  3. if pos != str.size() then the whole line in not an integer (or if the above throws an exception), then it's not a valid integer, else return the value...
Sign up to request clarification or add additional context in comments.

Comments

1

Read a line of input as a std::string using std::getline().

Examine the string and check if it contains any characters that are not digits.

If the string only contains digits, use a std::istringstream to read an integer from the string. Otherwise report a failure, or take whatever other recovery action is needed (e.g. discard the whole string and return to read another one).

7 Comments

I thought of doing something like this but I wanted to know if there was a "cleaner" way of doing it by just working with an integer, without converting from string to integer
how is isstringstream used to convert a string to an int after I have checked that all its characters are digits? I havent used atoi or stoi before, what is the difference?
Depends on what you mean by "cleaner". If you mean simply reading from the stream and having it do all the checking for you, then no. Seeing as your whole purpose is to extract integral values from user input, there will always be some form of conversion to an integer. The manner of the conversion depends on how you handle user input. One advantage of using a string is that it allows multiple passes on the data if desired (e.g. one pass to check if input is valid, another pass to extract a value if the input is clean).
I am doing it this way now but I am having trouble with converting the string to an int. i am trying to understand how atoi and stoi work.
Assuming you've prechecked the string contents, "std::istringstream str(your_string); str >> integer_value" will work.
|
1

You could use a stringstream

int read_office_num()
{
    //Declaration of a local variable
    int office_num;

    string input = "";

    while (true) {
        getline(cin, input);
        stringstream myStream(input);
        if (myStream >> office_num)
            break;
        cout << "\nInvalid office number, it should only consist of digits!! Enter another:\n" << endl;
    }

    return office_num;
}

If you want to reject input like 123 xxx you could add an additional check to verify that the received string is indeed an integer:

bool is_number(const string& s)
{
    string::const_iterator itr = s.begin();
    while (itr != s.end() && isdigit(*itr)) ++itr;
    return !s.empty() && itr == s.end();
}

int read_office_num()
{
    //Declaration of a local variable
    int office_num;

    string input = "";

    while (true) {
        getline(cin, input);
        stringstream myStream(input);
        if (is_number(input) && myStream >> office_num)
            break;
        cout << "\nInvalid office number, it should only consist of digits!! Enter another:\n" << endl;
    }

    return office_num;
}

3 Comments

I believe that the OP wants to reject input like "123 abc" which your routine would accept. I also assume that //Read input cin >> input; is left over from an earlier edit (because you getline() after that).
... which can be done by reading from the stream again and checking that you've reached EOF.
well I am assuming the user will input a single piece of data so I meant to reject input like "123abc".
1

You should probably just look at the number of input characters that are left in cin. You can do that with in_avail

Your function will probably end up having a body something like this:

//Declaration of a local variable
int office_num;

//Read input and check if input was valid
for (cin >> office_num; cin.rdbuf()->in_avail() > 1; cin >> office_num){
    //Print error message
    cout << "\nInvalid office number, it should only consist of digits!! Enter another:\n";
    //Ignore any whitespace left on input stream by cin
    cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

//Office number entered is valid
valid_office_num = true;

return office_num;

Points of interest:

  1. There is always at least 1 character in cin otherwise the cin would be marked as bad and that would not be good
  2. You don't need valid_office_num if read_office_num is implemented this way, cause valid_office_num will always be set to true before returning

Comments

0

Hm. I may be missing something, but why not read a line, trim it, use regular expressions to validate a number and then exploit strstream's facilities or just atoi if you must? In all reality I'd probably just let users get away with extraneous input (but discard it if I'm sure I'm always running interactively). Following the motto "be lenient in what you accept."

The "interactive" caveat is important though. One can generally not assume that cin is a terminal. Somebody may get cocky and let your program run on a text file or in a pipeline, and then it would fail. A robust approach would separate data processing (portable) from means of input (possibly machine specific and therefore also more powerful and helpful than stdin/stdout via a console).

Comments

0

Here's how to do it using Boost Lexical Cast:

#include <boost/lexical_cast.hpp>
#include <iostream>
#include <vector>
#include <string>

int read_office_num()
{
    using boost::lexical_cast;
    using boost::bad_lexical_cast;
    using namespace std;

    int office_num;
    while (true)
    {
        try
        {
            string input = cin.getline();
            office_num = lexical_cast<int>(*argv));
            break;
        }
        catch(const& bad_lexical_cast)
        {
            cout << "\nInvalid office number, it should only consist of digits!! Enter another:\n";
        }
    }

    return office_num;
}

Comments

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.