4
\$\begingroup\$

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 into std.

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";
}
\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\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\$ Commented Oct 29, 2015 at 14:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.