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;
}
Yiannis
  • 929
  • 2
  • 11
  • 14
  • 1
    You can add header file "limits.h" and go for check that office_num should lie between INT_MAX and INT_MIN. – Prateek Shukla Apr 15 '15 at 11:38
  • possible duplicate of [integer input validation, how?](http://stackoverflow.com/questions/13212043/integer-input-validation-how) – NathanOliver Apr 15 '15 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. – Jonathan Mee Apr 15 '15 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. – Kerrek SB Apr 15 '15 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. – NathanOliver Apr 15 '15 at 12:21
  • @NathanOliver Good call, I do like [Loki Astari](http://stackoverflow.com/users/14065/loki-astari)'s thoroughness. But I don't prefer the `getline` method of dealing with this. – Jonathan Mee Apr 15 '15 at 12:26
  • @NathanOliver how do I get rid of the question duplication warning above my question? i have edited my question to explain how it differs – Yiannis Apr 15 '15 at 14:30

6 Answers6

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...
Nim
  • 33,299
  • 2
  • 62
  • 101
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;
}
ᴘᴀɴᴀʏɪᴏᴛɪs
  • 7,169
  • 9
  • 50
  • 81
  • 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). – Peter - Reinstate Monica Apr 15 '15 at 11:51
  • 1
    ... which can be done by reading from the stream again and checking that you've reached EOF. –  Apr 15 '15 at 11:52
  • well I am assuming the user will input a single piece of data so I meant to reject input like "123abc". – Yiannis Apr 15 '15 at 11:57
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).

Peter
  • 35,646
  • 4
  • 32
  • 74
  • 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 – Yiannis Apr 15 '15 at 12:01
  • 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? – Yiannis Apr 15 '15 at 12:38
  • 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). – Peter Apr 15 '15 at 12:41
  • 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. – Yiannis Apr 15 '15 at 12:44
  • 1
    Assuming you've prechecked the string contents, "std::istringstream str(your_string); str >> integer_value" will work. – Peter Apr 15 '15 at 12:54
  • @Yiannis "Cleaner" is relative.I personally don't like the `getline` method, because `cin` already knows if you've extracted everything. [My answer](http://stackoverflow.com/a/29649563/2642059) just extract's the `int` and uses `in_avail` to ask `cin` if there are more characters to be read. That doesn't necessarily make it "cleaner", but I prefer it. – Jonathan Mee Apr 15 '15 at 12:58
  • i was doing this `office_num_int = atoi(office_num_str);` but i get a compile error. i will try what you said. @Peter – Yiannis Apr 15 '15 at 13:01
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
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
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).

Peter - Reinstate Monica
  • 15,048
  • 4
  • 37
  • 62
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;
}
Daniel Lidström
  • 9,930
  • 1
  • 27
  • 35