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:
- Ask the user for desired input.
- Read a line of input
- Convert the line to what you expect.
- 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.
getline, parse them withistringstreamcininto astringobject, construct anistringstreamwith thatstringobject, and read from thatistringstreaminto myintobjects?istringstreamobject the same way you usecin.getline()and things stay synchronized.std::cinfor 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::getlinewithstd::stringstreammakes 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 withstd::cin, but cannot identify the line-end, without reading more thanintvalues, due tostd::cinignoring whitespace)