tags:

views:

134

answers:

7

I'm attempting to construct a function that will perform a sanity check on the user's response to a number of questions, each of which would ideally be a non-zero integer. How can I construct a function that would be able to accept a parameter of any data type, but only have a single parameter? For example:

bool SanityCheck(<type id> number)

where type id would cover any data type.

+2  A: 

Use templates:

template <typename T>
bool SanityCheck(T number);

The sanity check may vary for different types. As this is a homework, I won't post any more code just hint you with a Google search term "partial template specialization".

dark_charlie
A: 

Making your function a template function would achieve this.

template<typename T>
bool SanityCheck(T number);
SilverSun
+1  A: 

Option 1: use boost::variant if you want it to be a single function

Option 2: overload this function for all types that you need

mojuba
I think boost::any is more appropriate than boost::variant in this particular case. Anyway a simple function template would provide a superior alternative.
Armen Tsirunyan
+6  A: 

It's not clear exactly what you really want here. Unverified input from a user normally comes in the form of a string. Typically you read in a string, verify that it has the desired form (e.g., for an integer, all digits). If it has the right form, you convert that to the desired type, and use it. If it doesn't, you ask the user to re-enter their data, usually with a prompt like "Please enter an integer between 1 and 10".

A function template is sort of a direct answer to the question you asked, but I have a hard time imagining it being of any help in a situation like you've described. A function template is most often of use in cases where you have to carry out some operations that are syntactically the same across a number of types. For example, it lets you add two numbers, regardless of whether those happen to be of type short, int, long, float, double, long double, etc. That only works because they're really all numbers, and you can reasonably use + to add any of them together.

When you're dealing with some unknown input, that doesn't apply though -- you need to verify enough about the data to be sure the operation is sensible and meaningful before you can do much else with it; it's pretty hard to get a meaningful result from comparing (for example) 7 to a sunset.

Jerry Coffin
+2  A: 

Ok, I think I get what you actually want now.

I imagine your situation is something like this:

  1. Read some user input (maybe using std::cin).
  2. Check to make sure it is an int.
  3. Use the int if it is one.

If this is the case then you do not want a function that can handle different data types, because the user cannot enter different data types, he can only enter characters and you have to choose what datatype you want to store that as.

I think this is what you need:

bool valid = false;
int input = 0;
while (!valid)
{
  std::string inputStr;
  std::cin >> inputStr;
  valid = isInteger(inputStr);
  if (!valid)
    std::cout << "Please enter an integer." << std::endl;
  else
    input = atoi(inputStr.c_str());
}
std::cout << "You entered " << input << "!" << std::endl;

You're going to have to write isInteger yourself, but hopefully you get the idea.

Peter Alexander
I would just use `boost::lexical_cast` and catch the exception. Two-for-one.
GMan
@GMan: And why not read that integer _directly_ from `std::cin` and check whether this succeeds? Why the detour?
sbi
@sbi: Even better! :) (Though one needs to reset the stream afterward when it fails, which might be more "confusing" than getting a string (never fails?) and trying to convert it.)
GMan
@sbi: That won't catch potential errors. What if I enter `1.5`? Streaming into an `int` will just get the `1`, which may be undesirable. He might prefer to show an error in that situation.
Peter Alexander
@GMan: Yeah, I meanwhile saw that this would be a problem for a beginner and then added such a detour into my own answer. @Peter: Indeed. But then a novice is unlikely to come up with a much better `isInteger()` function soon.
sbi
It's not difficult to verify that the string `"1.5"` isn't an integer...
Peter Alexander
@Peter: Sure it is. :) You have to take into account locales and blah blah.
GMan
@Peter: In my locale that would be `1,5`. And even putting locales aside: I'm _very_ sure, given that task, many of my students would have turned in a solution that would fail on trailing whitespaces. And those are just the first two cases we've come up with.
sbi
Trailing and leading whitespace would be ignored by streaming into a `std::string` :) and locales aren't even important here either: just reject anything that contains non-numeric characters (with a possibly allowed unary `-` at the beginning).
Peter Alexander
A: 

A lot of online surveys that I'm asked to fill out don't ask me to enter data but only select an option from 1 to 5. 1 = Totally Agree, 5 = Totally Disagree. This seems a more efficient way of collecting user input since you have total control over data type and all I have to do is highlight an option box.

John
+3  A: 

C++ is a statically typed language. What type a variable is of will be fixed at compile-time and cannot be changed at run-time. What users enter, however, will only be known at run-time, and cannot be known at compile-time. Therefore your question makes no sense.


When you expect an integer from a user, then the best way would be to try to read an integer, and check whether this succeeds:

int i;
std::cin >> i;
if(!std::cin)
  throw "Stupid user blew it!"; // or some real error handling

However, the catch with this is that, once an input operation fails, an input stream enters a bad state and the data that couldn't be read stays in the input buffer. If you want to handle this gracefully, would have to clear the stream's error state flags, and make it ignore whatever is in the input buffer.

So sometimes it might be easier to first read a string

std::string input;
std::cin >> input;                   // either read up to any whitespace, or 
std::getline(std::cin, input);       // newline, or 
std::getline(std::cin, input, '\t'); // tab, or whatever you want

because this always succeeds, and then try to convert it into whatever data you need. The way to do this is via string streams:

std::istringstream iss(input);
int i;
iss >> i;

Now you can check the string stream's state

if(!iss)

and if the conversion failed, std::cin will still be usable and the erroneous input read from its buffer.

However, there's one more catch: If a user inputs '"42 thousand"', then this won't catch the error. The remaining characters will be in the string streams input buffer and silently ignored. So what you usually need to do for such a conversion is to test whether the string stream's buffer is fully read, that is: reading reached EOF. You can check for this by invoking iss.eof(). However, if you read a whole line, there might be extra whitespace at the end, which you wouldn't want to make the conversion fail, so you need to read extra whitespace before you check for EOF: iss >> std::ws. (std::ws is a stream manipulator that "eats" consecutive whitespaces.)
by now, the conversion would look like this:

std::istringstream iss(input);
int i;
iss >> i >> std::ws; // you can chain input
if(!iss.eof())
  throw invalid_input(input);

Of course, this is pretty elaborated for a one-time conversion and I wouldn't exactly swear by the life of my kids that there isn't a nice improvement left that I hadn't thought of yet. So you would at least want to wrap this into a function and put that into your toolbox for reusing it (and improving on it, if you find an error):

bool convert_to_int(const std::string& str, int& result)
{
  std::istringstream iss(input);
  iss >> result >> std::ws;
  return iss.eof();
}

Or, generic for any type:

template< typename T >
bool convert_from_string(const std::string& str, T& result
{
  std::istringstream iss(input);
  iss >> result >> std::ws;
  return iss.eof();
}

Even better would be to use a ready-made off-the-shelf solution for this. Boost has just such a thing with its lexical_cast.


Here's a skeleton algorithm for the whole input routine:

int i;
do {
    read string input
    convert to int i
while(!conversion succeeded);

With the bits from further above, you should be able to fill in the missing parts.

sbi
I've [answered this kind of question](http://stackoverflow.com/questions/1243428/convert-string-to-int-with-bool-fail-in-c/1243435#1243435) somewhat similarly before, it shows how to implement a `lexical_cast`.
GMan
You stole my answer! :-O
Peter Alexander
@Peter: I'm sorry, I just couldn't help it. `:)`
sbi
Good answer. You may want to rename the second `convert_to_int()` to something like `convert_to_generic()`
ArunSaha
@ArunSaha: I'm not sure how I could have missed that. One of those pastos, I guess. Thanks!
sbi