tags:

views:

742

answers:

10

I'm in an intro to C++ class and I was wondering of a better method of checking if input was the desired type.

Is this a good way of doing this? I come from a PHP/PERL background which makes me rather apprehensive of using while loops.

char type;
while (true) {
    cout << "Were you admitted? [y/n]" << endl;
    cin >> type;

    if ((type == 'y') || (type == 'n')) {
        break;
    }
}

Is this a safe way of doing this or am I opening myself up to a world of hurt, which I suspect? What would be a better way of making sure I get the input I want before continuing?

+1  A: 

That's fine. If you want it to time out after a number of failures, you could use the i<10 but its not worth it.

Daniel A. White
+4  A: 

Use can use

do {
    program;
} while (condition_to_repeat);

if the algorithm is similar to your example. Otherwise the example is "safe", but I am not sure about the readablity.

Notinlist
+24  A: 

Personally I'd go with:

do
{
    cout << "Were you admitted? [y/n]" << endl;
    cin >> type;
}
while( !cin.fail() && type!='y' && type!='n' );
McAden
This isn't good. If `cin` gets closed it will loop forever spewing "Were you admitted? [y/n]" to `cout`. It's important to always test the success of the input operation before testing what it's supposed to have read.
Charles Bailey
Much more readable and concise than my code! Are Do-Whiles what is commonly used for make sure input is correct and if not to ask again?
Levi
Do-Whiles are mostly used in cases that you want to take some action before checking the condition, and this action would be repeated if your condition is true. In case of user input, before you check the condition you want to get some input, so Do-Whiles are convenient.
Alex
Do-Whiles are generally used whenever you want the code to execute at least once.
froadie
Edited per Charles Bailey's comment - he's exactly right. And Alex's comment answers Levi's comment. You could use a normal while loop in this case if you initialized type to something other than y or n.
McAden
Is it any better? If `cin` fails it still loops forever.
Charles Bailey
You need to call `cin.clear()`.
Potatoswatter
@McAden The condition should be (!cin.fail()
Alex
@ Charles - forgot some of the intricacies of using cin, thanks. @Alex - How's that any different?
McAden
Alex
If `cin` really has been closed then calling `clear()` may be futile. The next read attempt would immediately re-set the fail bit and the infinite loop writing to `cout` remains.
Charles Bailey
Misread and wasn't actually thinking about a CLOSED stream. I missed that in Charles' comments. Charles and Alex are both correct.
McAden
The clear/ignore is, of course, wrong depending on what input was last performed (before this loop). You should always read a full line when doing input (if doing line-based input) rather than randomly leaving a newline or not. `!cin.fail()` is the same as `cin`.
Roger Pate
24 upvotes, 500 people discussing a problem that comes right after 'hello world', and the end result is something that will immediately hang on the ignore and not even begin to work. I don't know which is more pathetic, C++ iostreams or that people will argue over the finest of details but never bother to run the damn thing.
FranticPedantic
+2  A: 

Why not do it this way?

do
{
    cout << "Were you admitted? [y/n]" << endl;
    cin >> type;
}while(  type !='y' && type !='n');
Erich
How do you know that `cin >> type` worked?
Charles Bailey
I considered the target... this is an intro to C++ class. It is assumed in these early classes that cin is connected to the command-line/OS, and that it doesn't fail.
Erich
^Z in Windows or ^D in Unix will cause the command-line cin to return EOF the first time and FAIL the second time.
jmucchiello
@jmucchiello: It will set failbit the first time when it reaches EOF trying to read the char. (The char cannot be read, which must be read, so the stream has 'failed'.)
Roger Pate
Okay. Regardless, it still will never exit.
jmucchiello
A: 

I've never coded in PERL/PHP before but based off of your question and the example here's a simple solution.

    char c;
while(true){
    cout << "Were you admitted? [y/n]" << endl;
    cin >> c;
    if(c == 'y')
        break;
}
cin.get(c);
return 0;

You will continue to be prompted until you enter a 'y' after entering a 'y' this simple program will exit.

ChadNC
What does this solve?? Did you just decide to assume the example snippet is the entire `main()`?
Potatoswatter
So, you just took Levi's code snippet (which was the question) removed the equality to 'n' condition (?!?!?!??!?!) and post it as a solution?!?!?!?
Alex
+7  A: 

Personally I'd make the prompt a separate function, this makes it putting the prompt output and reading a response a logical expression to put in a while loop.

Testing whether the read was successful is critical to the correct functioning of the code.

I'd also prefer to use std::getline to get a line at a time as it helps reduce errors caused by reading the rest of a half read line that was the result of a partial read to earlier user responses.

bool PromptForChar( const char* prompt, char& readch )
{
    std::string tmp;
    std::cout << prompt << std::endl;
    if (std::getline(std::cin, tmp))
    {
        // Only accept single character input
        if (tmp.length() == 1)
        {
            readch = tmp[0];
        }
        else
        {
            // For most input, char zero is an appropriate sentinel
            readch = '\0';
        }
        return true;
    }
    return false;
}

void f()
{
    char type = '\0';

    while( PromptForChar( "Were you admitted? [y/n]", type ) )
    {
        if (type == 'y' || type == 'n')
        {
            // Process response
            break;
        }
    }
}
Charles Bailey
+1. Use a for loop to scope the variable. Returning the stream is more versatile and still allows testing as if it was a bool.
Roger Pate
+1  A: 

And don't forget to make your potential user's life easier explaining each step and even provide the case insensitive input.

#include <iostream>

#define MAX_USER_INPUT_ATTEMPTS 3

int _tmain(int argc, _TCHAR* argv[])
{
char input_value = ' ';
int current_attempt = 1;

while(true)
{
    std::cout << "Please confirm your choice (press y[es] or n[o] and Enter): ";

    std::cin >> input_value;

    input_value = tolower( input_value );

    if(input_value=='y' || input_value=='n')
    {
        break;
    }
    else
    {
        std::cout << "You have used " << current_attempt << " of " << MAX_USER_INPUT_ATTEMPTS << " attempts" << std::endl;
        ++current_attempt;
    }

    if( current_attempt > MAX_USER_INPUT_ATTEMPTS )
    {
        std::cout << "Warning: Maximum number of attempts reached." << std::endl;
        break;
    }
}

return 0;
}
opal
A: 

The do...while construct is sort of made for this, but you can also do all the work in the loop condition:

while (std::cout << "Were you admitted [y/n]\n" && std::cin >> answer && !(answer == 'y' || answer == 'n'));

:)

And if you don't want to test the success of std::cin >> answer (e.g want to loop infinitely with Ctrl+Z), you can replace && for comma :)

Not entirely serious, even though methinks putting the prompt in the condition can sometimes enhance the logic of such loops (avoid break).

UncleBens
A: 

I see two "problems" here. First one is the use of while( true ). I don't think that's a good practice (though probably this is a matter of taste for many people). Breaking inside a loop is however interesting for searching:

for(i = 0; i < MAX; ++i) {
  if ( v[ i ] == searchedElement ) {
      break;
  }
}

This is self-explanatory: you run until the end of the vector, though if the element is found, you break before reaching it. It is justifiable.

About getting information from the console, you can run into a lot of trouble reading directly from cin, for example, being returned the contents only until the first space if the input has one. getline() is an utility function that reads to a string, which is (nearly) always safe. getline() is defined in the utility header. You do also need the string header. And cstdio if you want to use EOF.

int main()
{
    int ch;
    std::string type;

    do {
        getline( std::cin, type );

        if ( cin.fail() ) {
            ch = EOF;
            break;
        }

        ch = tolower( type[ 0 ] );
    } while( ch != 'y' && ch != 'n' );

    // interesting things here...

    return 0;
}
Baltasarq
A: 

Line-based input does not have to be verbose, you can make it succinct, with a single function you write once, that still handles corner cases:

bool yesno_repeat(char const* prompt) {
  using namespace std;
  while (true) {
    cout << prompt << " [yn] ";
    string line;
    if (!getline(cin, line)) {
      throw std::runtime_error("unexpected input error");
    }
    else if (line.size() == 1 and line.find_first_of("YyNn") != line.npos) {
      return line == "Y" || line == "y";
    }
  }
}

int main() try {
  if (yesno_repeat("Blow up?")) {
    take_off_every<Zig>(); // in the future, a zig is a nuclear missile...
  }
  return 0;
}
catch (std::exception& e) {
  std::cerr << e.what() << '\n';
  return 1;
}
Roger Pate