Search code examples
c++cingetlineio-redirectionistream

Using cin for keyboard input after processing redirected file input with getline


I know, this question has been treated tons of times.. but I can't make it work anyway.. here I paste some code:

#include <sstream>
#include "header.h"
using namespace std;
using namespace header;

int main(){
    string parent, child, line, node;
    int choose = 0;
    tree_ptr tree_inst = new tree;
    cout << "*** Start ***" << endl;    
    while (getline(cin, line, '\n')){
        istringstream line_stream(line);
        line_stream >> parent;
        if (parent.compare("0") == 0) break;
        while (line_stream >> child) {
            if (!tree_inst->insert(parent, child)) {
                cout << "*** ERROR! ***" << endl;
                tree_inst->visit(tree_inst->root);
                cout << "*** End ***" << endl;
                return -1;
            }
        }
    }
    while (true) {
        cin >> choose;  //<== doesn't wait for the input, just loop forever
                        //    on the default statement of the switch...
        switch (choose) {
            ...
        }
    }
}

I've already tried to insert some cin.sync() cin.clear() cin.ignore() ..etc.. but nothing changed!


Solution

  • The first getline() loop would loop forever, if you don't break it in one way or another. If done in a clean manner, this shouldn't be an issue at all. In fact I could not reproduce your error.

    How to analyze the error

    So the cause is either a bad sate of cin or non numberic non space input that is still pending. To help you find out, I suggest you add some diagnostic code:

    cout << "Enter free lines of text or stop to return to the menu:";
    while (getline(cin, line, '\n')) {   // This would loop foreved
        if (line == "stop")              // I added this one to exit
            break;                       // but how do you do ?
    }
    while (true) {                       // I get no problem here
        cout << "Cin status: failed="    // <===Add this simple diagnostic to 
             << cin.fail() << " bad="    //     check the state of the stream 
             << cin.bad() << " eof=" << cin.eof() << endl;  
        cout << "Next char in decimal is:" // <=== Add this to show 
             << cin.peek() << endl;        //      where in the stream you're hanging
        cout << "Choose:";
        cin >> choose;  
        cout << "selected: " << choose << endl;
    }
    

    If your stream state is not clean, despite a cin.clean() , it's because either you closed the stream or typed in an end-of-file code at the console (Ctrl+D or Ctrl+Z depending on system) .

    It the stream state is clean but the peeked char is not numeric (i.e. decimal code not between 48 and 57), it's the bad input that sets the stream in a fail state.

    Edit: your specific case

    Looking at the diagnostic you've provided (fail=1, eof=1), it appears that after the first loop you've already eached the end of the input. So your input fails because there is no further data to read. Your input file on pastebin confirms that the last line is the "0" that you use to exit the first loop.

    Following our exchanges, I understand that you've in fact redirected input from the command line (e.g. yourprogramme <yourinput.txt ) and expect your code to switch back to keyboard input once the redirected file input reaches its end. Unfortunately this is not the way it works. If you redirect input from a file, cin will always refer to the redireted file.

    To be able to mix file and keyboard input, you need to use <fstream> and more precisely ifstream for reading from the file and keep cin for keyboard entry.

    Here is a slightly modified code that takes the filename from the command line but without indirection (e.g. yourprogramme yourinput.txt ):

    ...
    int main(int argc, char**argv)
    {
        if (argc != 2) {     // if called from command line wrong arguments
            cerr << "You must provide the name of the data file as command line argument\n";
            return EXIT_FAILURE;
        }
        ifstream file(argv[1]);     // open the file for reading 
        if (!file) {                // if open failed, end the programme
            cerr << "Could not open "<<argv[1]<<"\n";
            return EXIT_FAILURE;
        }
        string line;                // here your code from before
        int choose = 0;
        cout << "*** Loading "<<argv[1]<< " ***" << endl;
                                  // but in the first loop replace cin with file
        while (getline(file, line, '\n')){   
            cout << "Read: " << line<<endl;
            if (line == "0")                // Abridged version;-)
                break;
        }
        file.close();               // File is no longer needed here.  
        while (true) {              // Second loop, unchanged, using cin  
            cout << "Choose (9 to exit):";
            if (!(cin >> choose))   // avoid looping forever if problem on cin
                break;
            cout << "selected: " << choose << endl;
            if (choose == 9)
                break;  
        }
    return EXIT_SUCCESS;
    }