patterncppCritical
Easier user input in C++
Viewed 0 times
inputusereasier
Problem
A more proper version of this utility can be found at the following link here: Giveth me thy easier user input in C++ - follow up.
I've always been a little bothered by the fact, that in order to get user input in C++, one had to use up to three lines of code in an ugly manner like the below to get user input for a specific type, with a prompt:
So, in order to make this process easier, I've created an
easy_input.h
main.cpp (tests)
I'd (preferably) like to know the following things:
I've always been a little bothered by the fact, that in order to get user input in C++, one had to use up to three lines of code in an ugly manner like the below to get user input for a specific type, with a prompt:
int user_input;
std::cout > ";
std::cin << user_input;So, in order to make this process easier, I've created an
easy_input function which allows for the user to specify a type, a prompt, and set user input to a variable, all in one line.easy_input.h
#ifndef EASY_INPUT_H_
#define EASY_INPUT_H_
#pragma once
#include
#include
// We're simply "re-defining" the standard namespace
// here so we can patch our easy_input function into
// it for the user's sake.
namespace std
{
template
TInput easy_input(const std::string& prompt);
}
/**
* This function serves as a wrapper for obtaining
* user input. Instead of writing three lines every
* time the user wants to get input, they can just
* write one line.
* @param {any} TInput - The type of the input.
* @param {string} prompt - The prompt to use for input.
* @returns - Whatever the user entered.
*/
template
TInput std::easy_input(const std::string& prompt)
{
TInput user_input_value;
std::cout > user_input_value;
return user_input_value;
}
#endifmain.cpp (tests)
#include
#include
#include "easy_input.h"
int main()
{
const std::string user_input1 = std::easy_input(">> ");
std::cout (">> ");
const int user_input3 = std::easy_input(">> ");
std::cout << user_input2 + user_input3 << "\n";
}I'd (preferably) like to know the following things:
- Am I using templates appropriately? I feel like I might have done something wrong here in the process.
- Is there anything that can be improved performance-wise?
- Is there a need for include guards?
- Is it okay to patch
easy_inputintostdwithout pr
Solution
namespace stdOthers have said this already but it's important enough to be repeated: Don't put your own definitions into
namespace std. It's undefined behavior.About the only thing I'm aware of that you may put into
namespace std are specializations for templates already defined by the standard. So, for example, if you havestruct MyType
{
int a;
};
inline constexpr bool
operator==(const MyType& lhs, const MyType& rhs) noexcept
{
return lhs.a == rhs.a;
}then you may do
#include // std::hash
namespace std
{
template <>
struct hash
{
using argument_type = MyType;
using result_type = std::size_t;
result_type
operator()(const argument_type& mt) const noexcept
{
return mt.a;
}
};
}so you can, say, use
MyType as key in a std::unordered_map.The whole point of
namespaces is to separate stuff. So put your own stuff into your own namespace. For example, Boost has its stuff in namespace boost and sub-namespaces thereof. You could use namespace ethan_bierlein or something. In other languages, it is common practice to use one's domain as in package com.example.myproduct (assuming you own example.com) but I haven't seen this practice in C++ yet.Correctness
Consider the following program.
int
main()
{
const auto age = easy_input("How old are you? ");
std::cout << "Hello, " << age << " year old!\n";
}If compiled and run as this
$ ./a.out
How old are you? 17
Hello, 17 year old!
everything seems fine. However, if the user inputs nonsense,
$ ./a.out
How old are you? don't care
Hello, 0 year old!
$
the result is probably not what you'd expect from a well-designed user interface. However, it gets worse.
int
main()
{
const auto ounces = easy_input("How many ounces of beer dou you want? ");
const auto age = easy_input("How old are you? ");
//std::cout = 18)
std::cout << "Here are your " << ounces << " ounces of beer.\n";
else
std::cout << "Sorry, you're not old enough.\n";
}In action:
$ ./a.out
How many ounces of beer dou you want? too many
How old are you? Here are your 0 ounces of beer.
$
Don't worry if you cannot reproduce the results of the above two examples result of the second example. It's undefined behavior so anything (including but not limited to your cat getting pregnant) could happen.
What's the reason for this?
There are two problems. First, in
TInput user_input_value; // (1)
std::cout > user_input_value; // (3)
return user_input_value; // (4)if
TInput is a builtin type like int, the variable user_input_value is not initialized on line 1. If the input on line 3 succeeds, the value is set to the input, which is fine. However, if the input is invalid, no value will be assigned and you'll return the uninitialized value. If the stream is good(), the extraction operator on line 3 will either successfully extract and assign the value or, if invalid input is given, set the failbit and assign 0. Therefore, if the first input is invalid, 0 will be returned (for ounces) and the failbit set. Then, on the second entry, nothing is assigned to user_input_value and an uninitialized int returned (for age). This results in undefined behavior.Running the above examples second (“too many ounces of beer”) example through a tool like Valgrind can unveil the error. (To my surprise, neither ASan nor UbSan were able to detect the error.)
I originally thought that if input fails for whatever reason, the destination value would never be changed. This seemed to be the case but apparently was changed in C++11 such that now 0 is assigned for invalid input provided the stream was
good() to begin with. Thanks to Mooing Duck for discovering this (see comments).The seemingly simple fix to this problem is to use value-initialization for
user_input_variable.TInput user_input_value {};Since your question is tagged with C++14, we can now at least use one C++11 feature (uniform initialization) with pride.
However, while this “solution” fixes the undefined behavior, it still has its issues. If the user enters invalid input, it should probably be yelled at and not 0 (or whatever a default-constructed
TInput is) returned silently. So you should really check the stream after the operation.TInput user_input_value {}; // value-initialization not strictly needed any more
std::cout > user_input_value)
return user_input_value;
throw std::istream::failure {"bad user input"};You could have the same effect by setting the
exceptions mask of std::stdin but this would also affect other uses of std::cin even outside your function so it might surprise users of your function. I'd consider a utility function messing with the mask bad.Some people will argue that a user inputting invalid data is by no means an “exceptional” event so throwing an exception is inappropriate. If you're like them, you might prefer returning a
Code Snippets
struct MyType
{
int a;
};
inline constexpr bool
operator==(const MyType& lhs, const MyType& rhs) noexcept
{
return lhs.a == rhs.a;
}#include <functional> // std::hash
namespace std
{
template <>
struct hash<MyType>
{
using argument_type = MyType;
using result_type = std::size_t;
result_type
operator()(const argument_type& mt) const noexcept
{
return mt.a;
}
};
}int
main()
{
const auto age = easy_input<int>("How old are you? ");
std::cout << "Hello, " << age << " year old!\n";
}int
main()
{
const auto ounces = easy_input<int>("How many ounces of beer dou you want? ");
const auto age = easy_input<int>("How old are you? ");
//std::cout << "ounces = " << ounces << ", age = " << age << "\n";
if (age >= 18)
std::cout << "Here are your " << ounces << " ounces of beer.\n";
else
std::cout << "Sorry, you're not old enough.\n";
}TInput user_input_value; // (1)
std::cout << prompt; // (2)
std::cin >> user_input_value; // (3)
return user_input_value; // (4)Context
StackExchange Code Review Q#107009, answer score: 66
Revisions (0)
No revisions yet.