1

I want to add the check-in my c++ code that user can not enter not integral values in reg. If he inputs, he is prompted again. I saw the solutions in the stack overflow that was of 2011 (How to check if input is numeric in C++). Is there some modern or good way now or is it same?

I tried using ifdigit() in ctype.h

// Example program
#include <iostream>
#include <ctype.h>
using namespace std;
int main()
{
    int x;
    cout<<"Type X";
    cin>>x;
    if(!isdigit(x))
    {
     cout<<"Type Again";
     cin>>x;
    }
}

but it didnt worked

here is my actual problem where I want to add check.

 cout << "Type Reg # of Student # " << i + 1 << endl;
 do
 {
      cin >> arr[i][j];
 } while (arr[i][j] < 999 || arr[i][j] > 9999);

where i and j are in dec. in for loop. I just want to add check that input is not string or something like this. Cant rely on 2011 answer

Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
Ahmad Anis
  • 2,322
  • 4
  • 25
  • 54
  • I read into a `std::string` and then go old school with `strtol` to make sure the whole `string `is converted. Using `std::stoi` is more modern, but it throws an exception on failure. Humans are one big bag of typos, so there's too much failure for me to call it exceptional. That said human input is so slow you probably won't notice the extra exception overhead.. – user4581301 Sep 14 '19 at 17:51
  • 1
    It really depends on what you need exactly, but`isdigit` is not going to give you any answer after to read input into `int` with `cin>>`. `cin>>` either succeded (and your `int` contains user input) or it failed (and `int` is left unchanged). Even without that, `isdigit` is for use with with `char`, not `int`. It checks whether character is between `'0'` and `'9'` (which for ASCII means it checks whether value is between `48` and `57`). It would be only useful if you read input as string first. – Yksisarvinen Sep 14 '19 at 17:53
  • I just read `strtol` and what I got is that it converts string which has some number in it to an integer. I just want to make sure that there is no way of adding string or char in my input. – Ahmad Anis Sep 14 '19 at 17:57
  • Ok good info related isdigit. now how do i check that user does not input a `string` or `char` in `int` data type – Ahmad Anis Sep 14 '19 at 18:01
  • 1
    Yeah, `std::strtol` will stop after reading all digits, but it also gives you means of checking if the whole input was used by the function. Check the second argument to that function (or in [`std::stoi`](https://en.cppreference.com/w/cpp/string/basic_string/stol) the second argument as well, but without two-star programming). – Yksisarvinen Sep 14 '19 at 18:01
  • As far as i understand about `std::strtol` or `std::stoi` it used to convert string or char to integer, what i want is that if user inputs some char or string , he should be prompted back to input some valid integer – Ahmad Anis Sep 14 '19 at 18:11
  • When you include C headers in C++ you should prepend `c` and not include the `.h`. So `ctype.h` would be `cctype`. – eesiraed Sep 14 '19 at 18:16
  • What makes you think a solution from 2011 has to be outdated? There has only been 2 new C++ standards since then and as far as I know none of them introduce features that would help here. The first 2 answers to that question are still perfectly good ways of doing this now. – eesiraed Sep 14 '19 at 18:26
  • Actually i am new to C++ so i want to know that there is modern solution or not. Even i tried those solutions but they didnt worked, I used `isdigit` and `cin.fail()` from that answer but didint worked – Ahmad Anis Sep 14 '19 at 18:27

2 Answers2

1

Check out the below example.

All the magic happens inside to_num(), which will handle white space before and after the number.

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

auto to_num(const std::string& s)
{
    std::istringstream is(s);
    int n;
    bool good = (is >> std::ws >> n) && (is >> std::ws).eof();

    return std::make_tuple(n, good);
};

int main()
{
    int n;
    bool good;

    std::cout << "Enter value: ";
    for(;;)
    {
        std::string s;
        std::getline(std::cin, s);

        std::tie(n, good) = to_num(s);
        if(good) break;

        std::cout << s << " is not an integral number" << std::endl;
        std::cout << "Try again: ";
    }
    std::cout << "You've entered: " << n << std::endl;

    return 0;
}

Explanation of what's going on inside to_num():

  1. (is >> std::ws >> n) extracts (optional) leading white space and an integer from is. In the boolean context is's operator bool() will kick in and return true if the extraction was successful.

  2. (is >> std::ws).eof() extracts (optional) trailing white space and will return true if there is no garbage at the end.

UPDATE

Here is a slightly cleaner version that uses Structured binding declaration and Class template argument deduction available in c++17:

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

auto to_num(const std::string& s)
{
    std::istringstream is(s);
    int n;
    bool good = (is >> std::ws >> n) && (is >> std::ws).eof();

    return std::tuple(n, good); // look ma, no make_tuple
};

int main()
{
    std::cout << "Enter value: ";
    for(;;)
    {
        std::string s;
        std::getline(std::cin, s);

        auto [n, good] = to_num(s); // structured binding
        if(good)
        {
            std::cout << "You've entered: " << n << std::endl;
            break;
        }
        else
        {
            std::cout << s << " is not an integral number" << std::endl;
            std::cout << "Try again: ";
        }
    }

    return 0;
}
  • 1
    Done. I will also post exception-free version in a few minutes. – Super-intelligent Shade Sep 14 '19 at 18:50
  • 1
    `std::stringstream ss(s);` constructs the `stringstream` with `s` as the input buffer. so you don't have to `ss << s`. Don't know if it's any faster, but it does allow you to use `std::istringstream` which can only be read, making it harder to add mistakes later. – user4581301 Sep 14 '19 at 19:00
  • @user4581301 you are correct on both points. They were copy-pasta artifacts from my old [conversion library](https://github.com/hrung/lib.orig/blob/master/convert.hpp). – Super-intelligent Shade Sep 14 '19 at 19:31
-1

If you properly handle errors, you'll end up with a prompt / input loop that looks something like this:

#include <iostream>
#include <limits>

int getInput() {

    while (true) {

        std::cout << "Please enter a number between 80 and 85: ";

        int number = 0;
        std::cin >> number;

        std::cout << "\n";

        if (std::cin.eof()) {
            std::cout << "Unexpected end of file.\n";
            std::cin.clear();
            continue;
        }

        if (std::cin.bad() || std::cin.fail()) {
            std::cout << "Invalid input (error reading number).\n";
            std::cin.clear();
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            continue;
        }

        if (number < 80 || number > 85) {
            std::cout << "Invalid input (number out of range).\n";
            continue;
        }

        return number;
    }

    // unreachable
    return 0;
}

int main() {

    int number = getInput();

    std::cout << number << std::endl;
}

We can omit the range check if we don't need it.

We handle std::cin.eof() (e.g. user presses ctrl+Z on Windows) separately from the other conditions, since for eof there's nothing to ignore.

This follows the standard C++ stream behavior for whitespace and number conversion (i.e. it will accept inputs with extra whitespace, or inputs that only start with numeric values).

If we want more control over what we want to accept, or don't want conversion to depend on the locale, we have to use std::getline to read input from std::cin, and then do the string conversion ourselves (either with std::stringstream as in Innocent Bystander's answer, or using std::strtol, or std::from_chars).

user673679
  • 1,327
  • 1
  • 16
  • 35