2

TL;DR

I am aware that if a program listens for EOF (e.g. ^D) as a sign to stop taking input, e.g. by relying on a conditional like while (std::cin) {...}, one needs to call cin.clear() before standard input can be read from again (readers who'd like to know more, see this table).

I recently learned that this is insufficient, and that the underlying C file descriptors, including stdin, need clearerr() to be run to forget EOF states.

Since clearerr() needs a C-style file descriptor, and C++ operates mainly with std::basic_streambufs and the like (e.g. cin), I want to generalise some code (see below) to run clearerr() on any streambuf's associated C-style file-descriptor, even if that may not be stdin.

EDITS (1&2):
I wonder if stdin is the only ever file-descriptor that behaves like this (needing clearerr() to run) ...?
If it isn't, then the following code should end the question of generalisation (idea pointed out by zkoza in their answer)

As zkoza pointed out in their comment below, stdin is the only file-descriptor that would, logically, ever need such treatment (i.e. clearerr()). Checking whether a given C++ stream is actually really attached to *std::cin.rdbuf() is all that is needed:

std::istream theStream /* some stream with some underlying streambuf */

if (theStream.rdbuf() == std::cin.rdbuf())
    clearerr(stdin);

Background

I'm writing a tool in C++ where I need to get multiple lines of user input, twice.

I know there are multiple ways of getting multiline input (e.g. waiting for double-newlines), but I want to use EOF as the user's signal that they're done — not unlike when you gpg -s or -e.

After much consultation (here, here, and on cppreference.com), I decided to use... (and I quote the third):

[the] idiomatic C++ input loops such as [...]

while(std::getline(stream, string)){...}

Since these rely on std::basic_ios::operator bool to do their job, I ensured that cin.rdstate() was cleared between the first and second user-input instructions (using cin.clear()).

The gist of my code is as follows:

std::istream& getlines (std::basic_istream<char> &theStream,
                        std::vector<std::string> &stack) {
    std::ios::iostate current_mask (theStream.exceptions());
    theStream.exceptions(std::ios::badbit);
    std::string &_temp (*new std::string);
    while (theStream) {
        if (std::getline(theStream, _temp))
            stack.push_back(_temp);   // I'd really like the input broken...
                                      // ... into a stack of `\n`-terminated...
                                      // ... strings each time
    }

    // If `eofbit` is set, clear it
    // ... since std::basic_istream::operator bool needs `goodbit`
    if (theStream.eof())
        theStream.clear(theStream.rdstate()
                        & (std::ios::failbit | std::ios::badbit));
                        // Here the logical AND with
                        // ... (failbit OR badbit) unsets eofbit
    
    // std::getline sets failbit if nothing was extracted
    if (theStream.fail() && !stack.size()) {
        throw std::ios::failure("No input recieved!");
    }
    else if (theStream.fail() && stack.size()) {
        theStream.clear(theStream.rdstate() & std::ios::badbit);
        clearerr(stdin); //  the part which I want to generalise
    }
    
    delete &_temp;
    theStream.exceptions(current_mask);
    return theStream;
}
mavenor
  • 168
  • 8
  • I'm a bit confused. From what I understand, `cin.clear()` has pretty much the same effect as `clearerr(stdin)`. – Adrian Mole Sep 25 '21 at 21:34
  • That's what I thought (and had hoped) too! But [apparently not](https://stackoverflow.com/questions/58732434/cin-clear-leave-eof-in-stream) :/ Thanks for replying tho! – mavenor Sep 26 '21 at 07:27

1 Answers1

1

This does what you need:

#include <iostream>

int main()
{
    std::cin.sync_with_stdio(true);
    char c = '1', d = '1';
    std::cout << "Enter a char: \n";
    std::cin >> c;
    std::cout << (int)c << "\n";
    std::cout << std::cin.eof() << "\n";
    std::cin.clear();
    clearerr(stdin);
    std::cout << std::cin.eof() << "\n";

    std::cout << "Enter another char: \n";
    std::cin >> d;
    std::cout << (int)d << "\n";
    std::cout << std::cin.eof() << "\n";
}

It works because C++'s std::cin is tied, by default, with C's stdin (so, the first line is actually not needed). You have to modify your code to check if the stream is std::cin and if so, perform clearerr(stdin);

EDIT:

Actually, sync_with_stdio ensures only synchronization between the C and C++ interfaces, but internally they work on the same file descriptors and this may be why clearerr(stdin); works whether or not the interfaces are tied by sync_with_stdio

EDIT2: Does these answer your problem? Getting a FILE* from a std::fstream https://www.ginac.de/~kreckel/fileno/ ?

zkoza
  • 2,644
  • 3
  • 16
  • 24
  • Hi! First, thanks for responding! As you pointed out yourself, I went ahead and resorted to checking if the `std::istream&` passed to my function _was_ indeed `std::cin` in the first place. I did this by testing for `theStream.rdbuf() == std::cin.rdbuf()`. – mavenor Oct 06 '21 at 13:33
  • Just realised what a terrible piece my original question is, so I'm going to go and tidy it up. The main objective was really to *find a way of **generalising `clearerr()`***. Now I doubt one will _ever_ come across a `streambuf` other than the one sync'd with `stdin` that will need `clearerr()`… Still like generalising tho – mavenor Oct 06 '21 at 13:41
  • So to sum up, if `stdin` isn't the only file-descriptor which might need `clearerr()` after encountering an EOF, how do I generalise `clearerr()` for any given `std::streambuf`? (which unfortunately can't be translated to a C-like file-descriptor) – mavenor Oct 06 '21 at 14:50
  • 1
    Why should any other stream be of any concern? For `std::cin` there's a "man-in-the-middle": the terminal that processes user's input. If `cin` is attached to a Linux terminal, it will never receive bytes corresponding to `ctrl+C`, `ctrl+Z`, `ctrl+U`, `ctrl+W`, `ctrl+D`. You can tell that `ctrl+D` was pressed only by examining the eof and fail flags (they're both set by ctrl+D). Why c++ compilers (at least gcc and clang) don't associate the state of a stream with the state as defined by the underlying C library is a real mystery to me. But they certainly do it on purpose. – zkoza Oct 06 '21 at 15:58
  • You have a really good point about the being an MITM! You're right, I suppose it *is* true that `stdin` is the only file-descriptor that needs special treatment. Thanks, [zkoza](https://stackoverflow.com/users/12451243/zkoza)! – mavenor Oct 07 '21 at 06:58