tags:

views:

791

answers:

7

This code works as desired for the most part, which is to prompt the user for a single character, perform the associated action, prompt the user to press return, and repeat. However, when I enter ^D (EOF) at the prompt, an infinite loop occurs. I am clearing the error state via std::cin.clear() and calling std::cin.ignore(...) to clear the buffer. What could be causing the infinite loop?

#include <iostream>
#include <limits>

void wait()
{
    std::cout << std::endl << "press enter to continue.";
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    std::cin.clear();
    std::cin.get();
}

int main()
{
    char response;

    while (true)
    {
        std::cout << "enter a character at the prompt." << std::endl << "> ";
        std::cin >> response;
        switch (response)
        {
            case 'q':
                exit(0);
                break;
        }
        wait();
    }
}

I am running this in the Mac OS X terminal, if it matters.


UPDATE: What I am really asking here is, when the user enters EOF (^D) at the prompt, how do I (a) detect it and (b) reset the stream so that the user can continue to enter data.

The following example is different from the code above, but illustrates the same principle of clearing the stream after a ^D has been detected and continuing to read from that stream.

> a
you entered: a
> b
you entered: b
> ^D
you entered EOF
> c
you entered: c
...
A: 

You'll need to clear the flags to get the stream to do much of anything after it encounters EOF.

Jerry Coffin
I believe that's what the call to std::cin.clear() is supposed to do...
titaniumdecoy
A: 

Err, I may be missing something, but I don't ever see you break out of the while (true) loop.

// ...
while (true) {
    if (std::cin.eof()) {
        break;
    }
    // ...
}
Bernard
That code is not shown as it is part of the (empty) switch statement. I want to continually read in single characters followed by a carriage returns (forever).
titaniumdecoy
I modified the code to make this more clear.
titaniumdecoy
A: 

Upon reading an EOF, you just ignore it and loop back, without exiting the loop, so you'll continually read the EOF and continually loop. If you want to do something on seeing an EOF, you need to handle it either in your switch or before.

Perhaps you want to read input from somewhere after the user has closed your stdin with ^D? In that case, you'll have to close cin and reopen it to read from the other place you want to read input from.

Chris Dodd
+3  A: 

You should always check whether any of a stream's failure flags are set after calling formatted extraction operation, in your example you are checking response without checking whether response was correctly extracted.

Also, you are using std::endl in your prompt output where it doesn't make sense. std::endl prints \n and then flushes the buffer, but you then immediately print more characters so the flush is redundant. As cin and cout are (usually) tied, calling an input function for std::cin will cause std::cout to be flushed in any case so you may as well put a \n into your prompt string and save on the verbose extra << operators.

Why not make a prompting function that prints the prompt, retrieves the input an returns a reference to the stream so that you can test it for success using the usual stream to boolean type conversion.

This way you can get rid of the while true and explicit break.

std::istream& prompt_for_input( std::istream& in, std::ostream& out, char& response )
{
    out << "enter a character at the prompt.\n> ";
    in >> response;
    return in;
}

int main()
{
    char response;

    while ( prompt_for_input( std::cin, std::cout, response ) && response != 'q' )
    {
        wait();
    }
}
Charles Bailey
Thanks for your answer, but your code has the same problem as mine. I need to CONTINUE THE LOOP after EOF (^D) has been entered by the user. As soon as I put prompt_for_input inside a loop that is not controlled by its return value (e.g., while(true)), I get an infinite loop.
titaniumdecoy
Basically what I want to do, (and I realize this isn't what I posted but it requires the same functionality) is to read a list of names entered by a user, one on each line, followed by EOF (^D). The program then needs to CONTINUE EXECUTION (e.g., reading from the std::cin) so that the user can respond to further prompts.
titaniumdecoy
Trying to restart a stream after EOF is just going to get you exactly the same problem. If the stream really is at the end, you're not going to be able to read anything from it. Resetting `eofbit` and carrying on only works in very limited circumstances. You say that the input should be 'one per line' so why do you need to wait for EOF as well?
Charles Bailey
One situation I am looking at is where the user enters a message on multiple lines and enters ^D on the last line to end the message.
titaniumdecoy
Are you saying it is not possible to reset std::cin once EOF has been read? If that is the case, could I route std::cin through another stream or something?
titaniumdecoy
Why not use a blank line or a dot on its own on a line as a signal? This would be far more portable. You've already ruled out any lines starting with `q`.
Charles Bailey
It can be done on some platforms but it's not the most portable or reliable solution - especially if you've redirected stdin. If you reset the `eofbit` and re-read you're likely to just get a failed read and the `eofbit` re-set.
Charles Bailey
There is an administrative program I use over ssh that prompts for input, and if you enter ^D, it simply redisplays the prompt. Is this not possible in standard C++?
titaniumdecoy
No, ^D is UNIX specific. It has no meaning in a Windows program. So standard C++ can only deal with standard streams. The administrative program you are talking about probably uses some Unix specific ioctl() call to allow ^D not to be considered EOF. That means it can process the ^D itself. If you want a non-portable method of handling ^D inside your Unix program check the man page for ioctl. (I think that's the page, Unix isn't my forte.)
jmucchiello
That sounds annoying, how do you actually get it to quit? `^D` is usually used to signal the end of input, that's what it's for and why it gets interpreted as such by the shell / C library to set the failbit and eofbit of `std::cin`. Sometimes things that 'loop' in response to `^D` are actually respawning a process that has closed. It is possible to reset the `failbit` and `eofbit` of a stream and re-attempt a read but this might just re-set those bits straight away. If it does you then need to check "have a failed twice in quick succession". All this because you are subverting what ^D is for.
Charles Bailey
A: 

As mentioned, you need to make sure the stream is not in a bad state. I would change while condition to use good(). Don't just check EOF as there are several ways a stream can become "bad" other than EOF.

while (std::cin.good()) {...
jmucchiello
But `isgood` is not a member function of `std::cin` ?
Charles Bailey
Wrong API. It's just called good() on C++ streams.
jmucchiello
A: 
while ((std::cout << "Enter a character at the prompt ")
      && (!(std::cin >> response) || response =='q')) {
 std::cout << "Not a valid input";
 std::cin.clear();
 std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

}

navigator
+2  A: 

The question does not really make sense for standard input. It will be hard to read something from standard input after that stream has ended -- you'll have to re-open it somehow, but there is no way to re-open standard input. It might be connected to a pipe, or to a file, or to a terminal -- and there's no behaviour suitable for all of these.

So you're going to be reading explicitly from the terminal, I assume. On UN*X systems, that means reading /dev/tty, and re-opening it when needed. Here's a simple example that does it; most error-checking omitted.

// Warning: UN*X-specific

#include <iostream>
#include <fstream>

using namespace std;

int main()
{
    for(unsigned i=0; ; i++) {
        ifstream tty("/dev/tty");
        if (! tty) {
            cerr << "Failed to open TTY" << endl;
            return 2;
        }
        string s;
        while (getline(tty,s))
            cout << i << ": " << s << endl;
    }
    return 0;   // (unreached)
}
ariels