2

I am writing a program in C++ that requires taking in integers from the user.

These may come in one of two forms:

  1. Single-input integers of the form 'X\n', where X is the user input. These get put into regular integer primitives.

  2. Multi-input integers of the form 'X X X X X\n', where X is the user input. These are parsed into an array of integers.

Here is an example of each:

Multi-input line

void UI::inputMatrix(Entries** matrix, int rows, int cols){
    for (int i = 0 ; i < rows; i++){
        std::cout << "Row " << i+1 << ": ";
        for (int j = 0 ; j < cols ; j++){
            std::cin >> matrix[i][j];
        }
    }
    std::cout << endl;
}

Single-input line

    std::cout << std::endl << "Please input the digit corresponding to your option: ";
    std::cin >> m_operation;

I've found that with std::cin functionality, this works perfectly fine unless the user begins to (a) not follow instructions or (b) type non-sensical input.

What is the best way to take this sort of integer input from the user?

I am considering writing my own program that parses string input from the user using std::getline() into the integers I require, and throws an error otherwise, but I really like the std::cin functionality available for taking in inputs into my array.

UPDATE:

Here is my solution:

istringstream readInput(){
    
    string s;
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    getline(cin, s);
    istringstream buffer(s);
    
    return buffer;
}

The istringstream can be used to put inputs into the designated locations and also acts as a boolean if an unexpected input is encountered.

6
  • 5
    Read lines with getline, parse them with istringstream Commented Dec 26, 2022 at 23:47
  • @paddy Do you mean that I would read lines from cin into a string object, construct an istringstream with that string object, and read from that istringstream into my int objects? Commented Dec 27, 2022 at 1:41
  • Yes, you use a istringstream object the same way you use cin. Commented Dec 27, 2022 at 18:54
  • Your solution assumes that the input is out-of-sync with the user’s press ENTER after every input expectation. The only time things get out of sync is when you mix formatted and unformatted input. Always read input with getline() and things stay synchronized. Commented Dec 28, 2022 at 5:46
  • 1
    For full error-handling with std::cin for integer input, you can see the answer to Hi I want user to enter 5 numbers and then output the sum in the end. If you have the option, std::getline with std::stringstream makes things easier (especially when reading multiple values per-line, such as with a .csv or tab-separated input where validating the contents of a line is important -- you can read multiple values with std::cin, but cannot identify the line-end, without reading more than int values, due to std::cin ignoring whitespace) Commented Dec 28, 2022 at 5:56

3 Answers 3

2

A good way to parse line-based input is to read lines using std::getline and then use std::istringstream to read formatted input from each line the same way you would do with std::cin.

For example:

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

int main()
{
    int line_num = 1;
    for (std::string line; std::getline(std::cin, line); ++line_num)
    {
        std::istringstream iss(line);
        for (int value; iss >> value; )
        {
            std::cout << "Line " << line_num << ": " << value << "\n";
        }
    }
}

Input:

1 2
3 4 5 fail 6
7
nope
8

Output:

Line 1: 1
Line 1: 2
Line 2: 3
Line 2: 4
Line 2: 5
Line 3: 7
Line 5: 8
Sign up to request clarification or add additional context in comments.

1 Comment

std::getline with std::stringstream is a bit easier for new users to follows that checking the three required steam-states and handling offending characters with std::ignore. Good solution.
0

input <int...> from the user

You have correctly observed that user input always ends with Enter. Good job!

You have also identified two specific patterns you wish to accept. This is fabulous!

The Algorithm

is very simple:

  1. Ask the user for desired input.
  2. Read a line of input
  3. Convert the line to what you expect.
  4. Complain (and maybe quit) if step 2 failed.

I know. Too simple.
But, alas, never obvious enough.

0. Ask for (and get) a line of user input

You have already identified this utility. (Excellent!) May I suggest this little improvement / correction:

std::istringstream ask_user_input( const std::string & prompt )
{
  std::cout << prompt;
  std::string s;
  getline( std::cin, s );
  return std::istringstream( s );
}

This automatically gives every user input the desired line-oriented synchrony. Ask for something, get the user’s Enter-terminated response.

BTW, I learned something from you. I would have just returned a std::string. Returning the std::istringstream is brilliant!

1. Single-input integers of the form 'X\n', where X is the user input.

Write a function:

int ask_integer( const std::string & prompt )
{
  auto input = ask_user_input( prompt );
  int n;
  if ((input >> n) and (input >> std::ws).eof())
    return n;
  throw error( "Expected an integer value" );  // (see end of post)
}

This is as easy to use as it looks:

int age = ask_integer( "age? " );

You will note that I have modified the input to be a bit forgiving with whitespace, but things like “12yrs” is still not valid.

If you wish, you can even add minimum and maximum checks (or any other kind of validation) to your little helper function, increasing its utility:

int age = ask_integer( "age? ", 0, 120 );

(Modifying the ask_integer() function to take optional bounds arguments is an exercise left to the reader.)

2. Multi-input integers of the form 'X X X X X\n', where X is the user input.

You may guess, correctly, that this is a variation on the last.

void ask_array_of_integer( const std::string & prompt, int * xs, std::size_t N )
{
  auto input = ask_user_input( prompt );
  for (std::size_t n = 0;  n < N;  n++)
    if (!(input >> xs[n]))
      break;
  if (!input or !(input >> std::ws).eof())
    throw error( "Expected ", N, " integers" );
}

template <std::size_t N>
void ask_array_of_integer( const std::string & prompt, int (&xs)[N] )
{
  ask_array_of_integer( prompt, xs, N );
}

Again, usage is simplicity:

int point[3];
ask_array_of_integer( "x y z? ", point );

Or:

int * xs = new int[5];
...
ask_array_of_integer( "5 ints? ", xs, 5 );

Observe how the workhorse is the C-style function. The C++ template overload is just an inline nicety.

You can even give the user multiple chances to get it right

We could just write a loop... with try and catch blocks...

But I’m just gonna get fancy here with a recursive template function taking a lambda, lol.

template <typename Lambda>
auto try_N_times( std::size_t N, Lambda f ) -> decltype(f())
{
  try
  {
    return f();
  }
  catch (const std::exception & e)
  {
    if (N > 1)
    {
      std::cout << e.what() << "\n";
      return try_N_times( N-1, f );
    }
    throw error( "Too many failures: ", e.what() );
  }
}

Here is the payoff for that madness: We can now be somewhat more forgiving with user input in a succinct, readable way.

int n = try_N_times( 3, [](){ return ask_integer( "n? " ); } );

int xs[5];
try_N_times( 3, [&xs](){ ask_array_of_integer( "xs? ", xs ); } );

error(...)

You can handle error processing however you like, of course.
Personally, I like this:

#include <stdexcept>
#include <sstream>
#include <string>

struct error : public std::runtime_error
{
  template <typename...Args>
  error( Args...args ) : std::runtime_error( join( args... ) ) { }
  
private:
  template <typename...Args>
  std::string join( Args...args )
  {
    std::ostringstream ss;
    (ss << ... << args);
    return ss.str();
  }
};

I do tend to keep it in an appropriate namespace, though.

4 Comments

Thanks for the response. Here is where I ended up (keep in mind that this is a function inside a class that is responsible for greater input handling): ` void UI::readInput(const InputType inputType){ retry: string s; getline(cin, s); if (!isValidInput(s, inputType)){ cout << "Invalid input. Try again: "; goto retry; } m_buffer = istringstream(s); } `
NOOOoooOOOoooo! I complimented you so much, and then you used goto. My poor heart. 🤢🤮😭😭😭 (LOL)
I like goto and I didn't see another solution. The way you did it with try and catch is clever, but wordy, and I like to keep it simple.
That’s honestly fine, but you could easily refactor that into a loop. It won’t pass a code review with goto. Remember, CS people like to burn goto witches.
-1

I came across this problem before, and the cin behavior really confuses me, if cin tries to read alpha into an integer, cin will not read until you manually use clear() function or another trick.

So here is my solution, it just works, maybe not elegant :)

vector<int> matrix(size);
int temp;

while (cin >> temp)
{
  matrix.push_back(temp);
  if (cin.peek() == '\n') break;
}
cin.clear();

1 Comment

std::cin with >> is for formatted input, and does not observe the fact that Every User Input Will End With ENTER. In other words, use unformatted input (string input) every time, then use a std::istringstream or something to process that input.

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.