I'm trying to write code that will give the user an error if they type anything besides a number, dont even know where to start
Printable View
I'm trying to write code that will give the user an error if they type anything besides a number, dont even know where to start
Check it out - MSDN has something to this effect on the cin reference page.
I think, though I may well be wrong, that using getline and entering the number as a string, is the most robust method. You can then validate and convert the string to another format or data type as required.
Validating a string is something you definitely need to learn anyway.
Alternatively, here’s a nice little routine that forces the input of a number using a do loop until you enter a predetermined number of your choice, -1 in this example.
I found this example on 1. Keyboard Input and it is very well explained.
But I’m not suggesting that this is how it should be done.
Of course it depends on your application and what you’re trying to achieve, which will be different every time. So don’t get hung up on any particular solution.Code:float x = 0;
string badChars;
do {
cout << "Enter a number (-1 = quit): ";
if(!(cin >> x)) {
cout << "The input stream broke!" << endl;
cin.clear();
cin >> badChars;
cout << "You typed \"" << badChars << "\" instead of a number." << endl;
cout << "Please try again." << endl;
}
else if(x != -1) {
cout << "You entered " << x << endl;
}
}
while(x != -1);
cout << "Quitting program." << endl;
If you want some code that's ready to use, or if you wish to study an advanced sample, here is an example of how to do it safely:
Basically, read a string, use string streams to convert to proper type, check if the input data is valid, then return it. If any of the steps fails, ask for the input again.Code:#ifndef INPUT_20081018_H
#define INPUT_20081018_H
#include <boost/format.hpp>
#include <iostream>
#include <string>
#include <sstream>
namespace Stuff
{
namespace Input
{
template<typename StrT> void StreamOut(const StrT& Output) { std::wcout << Output; }
void StreamOut(const char* Output) { std::cout << Output; }
void StreamOut(const std::string& Output) { std::cout << Output; }
template<typename T> void StreamOut(const boost::basic_format<T>& Output) { StreamOut(Output.str()); }
template<typename StrT, typename T> bool StreamIn(StrT& Stream, T& Buffer)
{
std::wstring Answer;
std::getline(Stream, Answer);
std::wstringstream StrStream(Answer);
void* SuccessPtr = (StrStream >> Buffer);
return SuccessPtr != nullptr;
}
template<typename T> bool StreamIn(T& Buffer) { return StreamIn(std::wcin, Buffer); }
// Read function overloads
// Read function with no validator func
template<typename DataT, typename StrT1>
void ReadNoVal(DataT& Out, const StrT1& Question)
{
Read(Out, Question, L"Invalid input!\n", L"Invalid input!\n", [](const DataT&){ return true; });
}
template<typename DataT, typename StrT1, typename StrT2>
void ReadNoVal(DataT& Out, const StrT1& Question, const StrT2& ErrorType)
{
Read(Out, Question, ErrorType, L"Invalid input!\n", [](const DataT&){ return true; });
}
template<typename DataT, typename StrT1, typename StrT2, typename StrT3>
void ReadNoVal(DataT& Out, const StrT1& Question, const StrT2& ErrorType, const StrT3& ErrorValidation)
{
Read(Out, Question, ErrorType, ErrorType, [](const DataT&){ return true; });
}
// Read functions with validator function
template<typename DataT, typename StrT1, typename Validator>
void Read(DataT& Out, const StrT1& Question, Validator ValidatorFnc)
{
Read(Out, Question, L"Invalid input!\n", L"Invalid input!\n", ValidatorFnc);
}
template<typename DataT, typename StrT1, typename StrT2, typename Validator>
void Read(DataT& Out, const StrT1& Question, const StrT2& ErrorType, Validator ValidatorFnc)
{
Read(Out, Question, ErrorType, L"Invalid input!\n", ValidatorFnc);
}
template<typename DataT, typename StrT1, typename StrT2, typename StrT3, typename Validator>
void Read(DataT& Out, const StrT1& Question, const StrT2& ErrorType, const StrT3& ErrorValidation, Validator ValidatorFnc)
{
for (;;)
{
StreamOut(Question);
if (!StreamIn(Out))
{
StreamOut(ErrorType);
continue;
}
if (ValidatorFnc(Out))
break;
StreamOut(ErrorValidation);
}
}
}
}
#endif // INPUT_20081018_H
Good luck.
Ah, real code, that’s what I’m looking for. Really appreciate you posting this, thank you. Beautiful.
Wow, thanks both of you. I actually have a thing where I won't use any code until I know everything about it, so I'll be doing a lot of research on the code you guys used =) Thanks again! These will be a great reference for me
You are free to ask questions in order go get a better understanding.
Really understanding what you're using is a noble goal, I think. Praiseworthy.
Alright, I'm trying to create my own version of the code you guys made. What I want to do is when the user inputs data into the win32 console and it's invalid (a letter), the letter is completely cleared out of the console, while keeping all other data on the screen. Then I'll put the blinking I bar (position to type) back where it was origonally.
Also #include <boost/format.hpp> is not in my library in Visual Studio 2010
I don't think you can do it without using things "external" to the standard C++, like WinApi or ncurses/pdcurses. Why not just make the user repeat the input process?
Elysia, your code is slightly too advanced for me at the moment, but i will definitely be looking into it in the future when i get into that type of code.
I actually have an idea of how I want to do it. Once an error is posted, I'll develop code that will find the position of the inputed data within the console and cout spaces instead for each character in the string. That is more towards the end of my code....i'm actually struggling right now getting my program to work the way I want it. Here is the code and i'll explain what I'm trying to do
Code:#include "stdafx.h"
#include <iostream>
using namespace std;
void error(int p)
{
char badChars;
if(!(cin >> p))
{
std::cin.ignore(std::numeric_limits<streamsize>::max(),'\n');
cout << "The input stream broke!" << endl;
cin.clear(p);
cin >> badChars;
cin.get();
}
else
cin >> p;
}
int _tmain(int argc, _TCHAR* argv[])
{
int x = 0, b = 0, c = 0;
cout << "Enter a (-1 = quit): ";
error(b);
cout << b;
cout << "Enter c (-1 = quit): ";
error(c);
cout << c;
return (0);
}
Alright, clearly you can see what I'm trying to do. I want the possibility of invalid input computed outside my main function, and if theres no error it computes it and moves on to the next step in the main function. I'm struggling with getting this to work the way I want, not to mention the value the you type in does not transfer over to the variable in the main function which i can't figure out how to do.
ignore the -1 = quit portion, i just want to check for invalid input and then put the value into the specified variable in my main function
You hardly see cin::clear called with an argument. That function is there to set a state for the stream, and by default, it is the good state. If you do call it with an argument, it's supposed to be a state variable. If this compiles, then I guess that means the state is based on int at some level. Anyway, that's not really pertinent knowledge for something like this.Code:void error(int p)
{
char badChars;
if(!(cin >> p))
{
std::cin.ignore(std::numeric_limits<streamsize>::max(),'\n');
cout << "The input stream broke!" << endl;
cin.clear(p);
cin >> badChars;
cin.get();
}
else
cin >> p;
}
To fix this, lets try analyzing the flow of what you want.
And of course, you write error because functions calling functions are a good way of going "outside".Quote:
I want the possibility of invalid input computed outside my main function,
You flip flopped steps. If we break it down even more, you ask the user for some data: std::cin >> input;Quote:
and if theres no error it computes it and moves on to the next step in the main function.
which either was an integer or not: if (cin)
If it was an integer you can go on to the next step in main: return;
If it was not an integer, you need to reset the stream: std::cin.clear();
And clean up the users mistakes: std::cin.ignore(std::numeric_limits<streamsize>::m ax(),'\n');
And get back to this step: std::cin >> input;
Now, as you may have realized, you can't get to that last step without controlling what the program executes next. Normally the program proceeds in top down fashion:
work;
work;
work;
But you need to keep trying std::cin >> input; until the user tries something that the computer recognizes as an integer.
You need a loop, and a way for main to receive the integer.
The difference between this and Elysia's code is night and day. Elysia's code is multipurpose, and you can impose some sort of rule through additional verification. This is for strictly C++ ints, and anything that fits in them is valid. If that is not sufficient, (and why would it be?) then there is an additional test inside the loop condition, to keep trying until you get exactly the integer you want.Code:int getInteger();
int main()
{
int p = getInteger();
return 0;
}
int getInteger()
{
int p;
std::cin >> p;
while (!std::cin)
{
std::cin.clear();
std::cout << "The input stream broke! gj" << std::endl;
std::cin.ignore(std::numeric_limits<streamsize>::max(),'\n');
std::cin >> p;
}
return p;
}
That code is too advanced for me too, at the moment, but I will keep working on it, or towards it.
If that boost library is a dead dependency, then what about the reference to boost on line 16?
I managed to download c++ Boost libraries, and run a little tutorial, but it was killing my already slow machine, so I uninstalled it.
Can you copy those boost headers to somewhere else and just leave those that you want to use. A bit confusing for a beginner.
I would at least like to get that code compiled.
Ah, right. You can comment that out.
I regularly work with boost and boost::format is a very nice library, so I added some support for it.
I'll probably write a short tutorial on using it sometime.
I hope you are successful soon, because I only rearranged statements you wrote in your attempt, for the most part.Quote:
That code is too advanced for me too, at the moment, but I will keep working on it, or towards it.
Hi Whiteflags! Lol, I’m working with Elysia’s code, which I got compiled at last.
The code I posted (#3) was just to show a fellow beginner programmer that there are alternatives you can mess around with. But I wasn’t suggesting this as a serious practise. Entering an overly large number would cause some problems too.
Input using string, validating string and then converting using stringstreams seems to be the most solid approach.
Hi Elysia! Looking forward to that tutorial, or at least a hint, because I haven’t as yet managed to work out how to use it, though I am learning about namespaces, which is good.
Thank you everyone; everything is useful.
So basically there are two functions that you should be concerned about:
Read and ReadNoVal.
Read takes a number of arguments and comes in several flavors.
Now, here is the general structure of how the function works:
- Print question.
- Retrieve input.
- Convert input to appropriate data type.
- If fail, print error message, goto beginning.
- Verify input by calling verification function.
- If verification function returns false, print error message, goto beginning.
- Everything works and the function returns.
Essentially, you call the function once to get some valid input. When the function returns, you have a valid input.
The argument the read function takes are:
- Reference to the variable to hold the input data.
- A string that contains the questions to print out before getting input.
- A string to print if the input cannot be converted into appropriate type.
- A string to print if the input doesn't pass the verification.
- A validator functor.
The strings can be of any type that can be printed. It can be std::string, const char*, etc. So long as they can be passed to std::cout or std::wcout. This opens up possibilities for your own string types, or boost's string types.
The validator function is a pointer to a function or a functor that returns a bool and accepts a reference to the converted input. It shall return true if the data is valid; false otherwise.
Note that the strings for valid inputs can be omitted, in which case, "Invalid input!" will be printed instead.
ReadNoVal is the same as Read, except it does not take a validation function. So that means if the input can be converted into the proper data type, the function returns.
The data type to convert to is determined by the type of the variable to hold the answer.
So, for example, say that we want an integer in the range of 100 - 200. We could write:
Assuming I haven't made any mistakes, this should compile.Code:int Num;
Stuff::Input::Read(Num, "Enter a number between 100 and 200: ", "That is not a number!",
"The number must be within the range 100 - 200!",
[](int Number) -> bool { return Number >= 100 && Number <= 200; });
I'm surprised something you want other people to use actually comes in a namespace called Stuff and not, oh, ElysiaWroteMe. It just seems like a dubiously available name.
It's a name I chose long, long, long ago, and I see no reason to change it. This code comes from my static library of code that I keep around. Essentially everytime I do something new, I integrate that code in an easier form into the "Stuff" library.
If I wanted people to know I wrote it, I could just add a copyright. But I don't see the point. It's code that I willfully share.
Thank you for the explanation. It all works fine now.
Of course I don’t wish to use this code. I just want to understand it so I can write my own, tailored to my own needs.
Thanks for sharing.