I've decided to take some of the many suggestions for improvement on my previous question, Easier user input in C++, and actually get it to work as expected. This time around, a few things are different, namely:
- The function will now accept string input with spaces, like
"Ethan Bierlein"
, and not just spit back the first "word" in the input. - The function now also accepts a different input stream rather than just
std::cin
, if, for some reason you do something like that. - The function now allows for a "no-prompt" option, as well, which just means that the argument
prompt
has a default value of""
. - There is an optional way to specify the type of the prompt as well now. It's default type is
std::string
. - The function, now named
get_input
, has it's own namespace,easy_input
, rather than being patched intostd
.
I do have a few concerns about my code though, and I'd like to hear your opinions on them:
- Is it a good idea to declare a static variable, just so I can have a default value for
prompt
? Is it a good idea to have a default value for prompt? - How necessary is it to provide an option to get input from a different input stream?
- Am I writing good C++, or am I doing certain things horribly wrong?
- Is there anything else that stands out for improvment?
easy_input.h
#if HAVE_PRAGMA_ONCE
#pragma once
#endif
#ifndef EASY_INPUT_H_
#define EASY_INPUT_H_
#include <iostream>
#include <string>
#include <boost/lexical_cast.hpp>
namespace easy_input
{
const std::string input_error_message = "an I/O error was encountered.";
static std::string default_prompt = "";
template <typename TInput, typename TPrompt = std::string>
TInput get_input(
const TPrompt& prompt = default_prompt,
std::istream& input_stream = std::cin
);
}
/**
* This function serves as a useful wrapper for getting user input.
* Rather than forcing the user to type out multiple lines every
* time they want to get input, they only have to type one line.
* @tparam TInput - The type of the input to obtain.
* @tparam TPrompt - The type of the prompt to be used. The default type is std::string.
* @param {TPrompt} prompt - The prompt to be used.
*/
template <typename TInput, typename TPrompt = std::string>
TInput easy_input::get_input(
const TPrompt& prompt = easy_input::default_prompt,
std::istream& input_stream = std::cin)
{
std::cout << prompt;
std::string user_input_value { };
if(!std::getline(input_stream, user_input_value)) {
throw std::istream::failure { easy_input::input_error_message };
}
return boost::lexical_cast<TInput>(user_input_value);
}
#endif
main.cpp (tests)
#include <iostream> #include "easy_input.h" int main() { std::string a = easy_input::get_input<std::string>("Enter your name please! "); std::cout << a << "\n"; int b = easy_input::get_input<int>("Enter an integer please! "); int c = easy_input::get_input<int>("Enter an integer please! "); std::cout << b + c << "\n"; std::string d = easy_input::get_input<std::string>(); std::cout << d << "\n"; int e = easy_input::get_input<int>(); int f = easy_input::get_input<int>(); std::cout << e + f << "\n"; }
1 Answer 1
Redefinition of default arguments
First of all, you cannot redefine default arguments. From [dcl.fct.default], slightly abridging the example:
A default argument shall not be redefined by a later declaration (not even to the same value). [ Example:
void m() { void f(int, int); // has no defaults f(4); // error: wrong number of arguments void f(int, int = 5); // OK f(4); // OK, calls f(4, 5); void f(int, int = 5); // error: cannot redefine, even to // same value }
—end example ]
So the definition of get_input
should just be:
template <typename TInput, typename TPrompt>
TInput easy_input::get_input(
const TPrompt& prompt,
std::istream& input_stream)
{ ... }
Otherwise, the code is ill-formed and should not compile (though apparently earlier versions of gcc happily allow this).
Include guards AND pragma
Given that you have include guards, it's unnecessary to additionally have the conditional #pragma once
. If you don't want to unconditionally use the pragma
, just stick with the include guards.
Default Prompt and Error
You use the error message in a single place - it'd be better to just write out the string literal there. It'll be easier to find when grepping through code, and there's no advantage I see in having the global constant.
With the default prompt, rather than all the additional complexity of default arguments, I think it'd be simpler to just add an overload:
template <typename TInput>
TInput get_input(std::istream& input_stream = std::cin);
template <typename TInput, typename TPrompt>
TInput get_input(const TPrompt& prompt,
std::istream& input_stream = std::cin)
{
std::cout << prompt;
return get_input<TInput>(input_stream);
}
Ultimately though...
My biggest takeaway from 5gon12eder's excellent answer to your original question was that there really isn't anything easy about this input, and it may just be easier to avoid it and stick to being explicit with the streams.
Here's something this solution can't handle. Let's say I want to input two ints. I could write:
int a, b;
std::cout << "$";
std::cin >> a >> b;
And end up doing something like:
42ドル 15
And that works. But if I tried to do:
int a = get_input<int>("$");
int b = get_input<int>();
and tried to input both on the same line... I'd get a lexical error since "42 15" isn't an int
. Maybe you're OK with that? But it's limiting and sadly not easy. Then again, nothing IO related in C++ is really easy anyway.
-
\$\begingroup\$ I was sort of aiming for more of a Python-esque input function, rather than what you describe in the end, so in the end, I am okay with that. \$\endgroup\$Ethan Bierlein– Ethan Bierlein2015年10月29日 14:33:18 +00:00Commented Oct 29, 2015 at 14:33