1

(this is possibly a duplicate of Why does std::basic_istream::ignore() extract more characters than specified?, however my specific case doesn't deal with the delim)

From cppreference, the description of istream::ignore is the following:

Extracts and discards characters from the input stream until and including delim.

ignore behaves as an UnformattedInputFunction. After constructing and checking the sentry object, it extracts characters from the stream and discards them until any one of the following conditions occurs:

  • count characters were extracted. This test is disabled in the special case when count equals std::numeric_limitsstd::streamsize::max()
  • end of file conditions occurs in the input sequence, in which case the function calls setstate(eofbit)
  • the next available character c in the input sequence is delim, as determined by Traits::eq_int_type(Traits::to_int_type(c), delim). The delimiter character is extracted and discarded. This test is disabled if delim is Traits::eof()

However, let's say I've got the following program:

#include <iostream>
int main(void) {
  int x;
  char p;
  if (std::cin >> x) {
    std::cout << x;
  } else {
    std::cin.clear();
    std::cin.ignore(2);
    std::cout <<   "________________";
    std::cin >> p;
    std::cout << p;
  
}

Now, let's say I input something like p when my program starts. I expect cin to 'fail', then clear to be called and ignore to discard 2 characters from the buffer. So 'p' and '\n' that are left in the buffer should be discarded. However, the program still expects input after ignore gets called, so in reality it's only get to the final std::cin>>p after I've given it more than 2 characters to discard.

My issue: Inputting something like 'b' and hitting Enter immediately after the first input (so 2 after the characters get discarded, 'p' and '\n') keeps 'b' in the buffer and immediately passes it to cin, without first printing the message. How can I make it so that the message gets printed immediately after the two characters are discarded and then << is called?

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
Pol
  • 185
  • 9
  • Total input is p\nb\n? – user4581301 Jan 08 '21 at 19:00
  • I forgot about it but it turns out '\r' is in there as well since it's WSL. – Pol Jan 08 '21 at 19:06
  • Can you be more clear about what you expect to happen? The code seems like it behaves properly regardless of line ending style, as the formatted input of the `char` (`b`) would bypass leading whitespace that `ignore` missed anyway, [leading to an output of `________________b`](https://tio.run/##bY5BDoIwEEX3PcUPbspCFy6BcBVTh4qTQNuUYkgMZ6/WuqlxVj/vz5sMOXcciWI8sKFpHTQ6tkvwWs29YBMwKzbyYXmo8RRAQlv7DnRXHi4lvkEuYWgaYoO@x5Y3gQztGtB1Wdqhp0UXNZsTTVp5Wbcl5dFYr@W54N9jQHX5marU0yPuj/lhu9hjdBDXFw) no matter what combination of line ending data and code translation is in play. What did you expect instead? – ShadowRanger Jan 08 '21 at 19:26
  • I'd expect my program to wait for input (for 'b') right after 'p' has been given and discarded (along with the accompanying '\n' or '\r') but right now, p (variable) is given a value from the buffer which is from before the message is printed. @ShadowRanger – Pol Jan 08 '21 at 19:35
  • @pol: So when you "immediately" type `pb` (we'll ignore possibility of carriage returns), you essentially want `b\n` (and any other input currently available in the buffer) to be thrown away, and only *new* stuff processed? That is, if `p\n` is entered, and there is a delay before `b\n`, you want to read the `b`, but if the `b` is there from the get-go, you want to discard it and wait for something else? – ShadowRanger Jan 08 '21 at 19:37
  • OK, basically: why doesn't(sorry if I'm not being straightforward enough) cin.ignore(2) get 'p\n' in this case, then immediately print the message '_____' and then 'wait' for input for the b variable? immediately after '____' 'b' gets printed, whereas I understand '___' should get printed, then prompt for input for b. Sorry for the confusion. @ShadowRanger – Pol Jan 08 '21 at 19:54
  • @pol: It did "prompt" for the `b` (in the sense of reading from `cin` and blocking if it's not there), but since `b` was already there, it just takes it immediately, there's noting to wait for. Sounds like you want it to discard the `b` and wait for something else, [my answer](https://stackoverflow.com/a/65635677/364696) covers the case of discarding all data currently available on `cin` (weird and non-portable, but you do you) by reference, and discarding the current line and the next line unconditionally if that was the intent. – ShadowRanger Jan 08 '21 at 20:04

3 Answers3

4

After a lot of back and forth in the comments (and reproducing the problem myself), it's clear the problem is that:

  1. You enter p<Enter>, which isn't parsable
  2. You try to discard exactly two characters with ignore
  3. You output the underscores
  4. You prompt for the next input

but in fact things seem to stop at step 2 until you give it more input, and the underscores only appear later. Well, bad news, you're right, the code is blocking at step 2 in ignore. ignore is blocking waiting for a third character to be entered (really, checking if it's EOF after those two characters), and by the spec, this is apparently the correct thing to do, I think?

The problem here is the same basic issue as the problem you linked just a different manifestation. When ignore terminates because it's read the number of characters requested, it always attempts to reads one more character, because it needs to know if condition 2 might also be true (it happened to read the last character so it can take the appropriate action, putting cin in EOF state, or leaving the next character in the buffer for the next read otherwise):

Effects: Behaves as an unformatted input function (as described above). After constructing a sentry object, extracts characters and discards them. Characters are extracted until any of the following occurs:

  • n != numeric_limits::max() (18.3.2) and n characters have been extracted so far
  • end-of-file occurs on the input sequence (in which case the function calls setstate(eofbit), which may throw ios_base::failure (27.5.5.4));
  • traits::eq_int_type(traits::to_int_type(c), delim) for the next available input character c (in which case c is extracted).

Since you didn't provide an end character for ignore, it's looking for EOF, and if it doesn't find it after two characters, it must read one more to see if it shows up after the ignored characters (if it does, it'll leave cin in EOF state, if not, the character it peeked at will be the next one you read).

Simplest solution here is to not try to specifically discard exactly two characters. You want to get rid of everything through the newline, so do that with:

std::cin.ignore(std::numeric_limits<std::stringsize>::max(), '\n');

instead of std::cin.ignore(2);; that will read any and all characters until the newline (or EOF), consume the newline, and it won't ever overread (in the sense that it continues forever until the delimiter or EOF is found, there is no condition under which it finishes reading a count of characters and needs to peek further).

If for some reason you want to specifically ignore exactly two characters (how do you know they entered p<Enter> and not pabc<Enter>?), just call .get() on it a couple times or .read(&two_byte_buffer, 2) or the like, so you read the raw characters without the possibility of trying to peek beyond them.

For the record, this seems a little from the cppreference spec (which may be wrong); condition 2 in the spec doesn't specify it needs to verify if it is at EOF after reading count characters, and cppreference claims condition 3 (which would need to peek) is explicitly not checked if the "delimiter" is the default Traits::eof(). But the spec quote found in your other answer doesn't include that line about condition 3 not applying for Traits::eof(), and condition 2 might allow for checking if you're at EOF, which would end up with the observed behavior.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
  • Thanks a lot for this but in your third to last example: when I do this, and so press `p` the underscores won't show until I give at minimum one more character, which is left in the buffer. That's what I don't understand, Is this abnormal behaviour? – Pol Jan 08 '21 at 20:19
  • @pol: If you change the `cout` line to `std::cout << "________________" << std::flush;` does the behavior change? Might you have a `std::cin.tie(NULL);` somewhere not shown [that's disabling the autoflush of `std::cout` when `std::cin` is polled](https://stackoverflow.com/q/31162367/364696)? – ShadowRanger Jan 08 '21 at 20:27
  • Nope, same thing @ShadowRanger – Pol Jan 08 '21 at 20:57
  • @pol: Okay, the problem is the opposite of what I thought, but at least I understand it now. Looking. – ShadowRanger Jan 08 '21 at 21:02
  • @pol: Okay, so yeah, your problem is essentially identical to [the problem you linked](https://stackoverflow.com/q/64204443/364696); `ignore` reads two characters, but it needs to know if the *next* character is the end of file so it can force the file into EOF state. I'll update answer. – ShadowRanger Jan 08 '21 at 21:07
  • 1
    @pol: Answer updated. Solution is either don't use `ignore` for a fixed count, use it for an unlimited count until a specified delimiter only, or read in a different way. – ShadowRanger Jan 08 '21 at 21:19
  • Thank you, it makes sense now ;) – Pol Jan 08 '21 at 21:26
1

Your problem is related to your terminal. When you press ENTER, you are most likely getting two characters -- '\r' and '\n'. Consequently, there is still one character left in the input stream to read from. Change that line to:

std::cin.ignore(10, '\n'); // 10 is not magical. You may use any number > 2

to see the behavior you are expecting.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • I think you're right (using WSL), however I don't exactly understand why that is. Regardless of that, shouldn't '\r\n' after 'b' still mean that, after '\r', it should stop reading from the input? – Pol Jan 08 '21 at 19:04
  • So why doesn't it immediately print the message after it discards '\r'? Sorry if it's obvious, I'm still confused about this. – Pol Jan 08 '21 at 19:07
  • @pol, I'll respond to that a bit late. Got pulled into something urgent. – R Sahu Jan 08 '21 at 19:09
  • @pol: I was about to correct this by noting that [`cin` on Windows is opened in text mode](https://stackoverflow.com/a/7587701/364696) so this shouldn't happen (`cin` would seamlessly treat a `\r\n` sequence as the single character `\n`), but if you're using WSL, you're not on Windows in the normal sense (it uses Linux rules, that don't do line ending conversions because Linux lines end with `\n` only), but you could easily be dealing with files generated by Windows tools that would use the `\r\n` line ending. – ShadowRanger Jan 08 '21 at 19:14
  • @ShadowRanger Regardless of the input format in the end, with `cin.ignore(2)` i still don't get why the program is expecting input, even if it reads 'p\n' or 'p\r\n' shouldn't it discard the first two and immediately print the message? but this is not whats happening here – Pol Jan 08 '21 at 19:20
  • On second thought, not sure this answers the question. The code should already be behaving properly, regardless of the line ending; the `ignore` skips `p\r`, then it outputs the underscore line, then the formatted `char` extraction skips leading whitespace (the extra `\n`) and extracts `b`, which is then printed. If the `\r\n` was just a `\n`, the only difference is there'd be no leading whitespace to skip before the `b`, but the behavior would be the same. @pol: Why do you think your original code is wrong? – ShadowRanger Jan 08 '21 at 19:20
  • When I [Try it online!](https://tio.run/##bY5BDoIwEEX3PcUPbspCFy6BcBVTh4qTQNuUYkgMZ6/WuqlxVj/vz5sMOXcciWI8sKFpHTQ6tkvwWs29YBMwKzbyYXmo8RRAQlv7DnRXHi4lvkEuYWgaYoO@x5Y3gQztGtB1Wdqhp0UXNZsTTVp5Wbcl5dFYr@W54N9jQHX5marU0yPuj/lhu9hjdBDXFw "C++ (gcc) – Try It Online"), adding a space character after the `p` in the input (simulating the spacing the `\r` adds), it still outputs `________________b`, same as when the space isn't there, which seems correct under all circumstances. – ShadowRanger Jan 08 '21 at 19:24
  • In the TIO it seems to be behaving properly: right after p, hitting enter ignores 'p\n' and immediately prints. However, I can't get this to happen in my terminal, even though it'd be discarding '\r\n' or '\n' it doesn't immediately print without passing at least one extra character. @ShadowRanger – Pol Jan 08 '21 at 19:31
  • @pol: I'm still not understanding. Do you just want the underscores to print immediately, even if if the user hasn't typed anything yet? Even if they don't hit enter after typing `b`? Something else? `cin` should be flushing `cout` automatically (you didn't call `cin.tie(NULL)`, right?), so the underscores should appear immediately, but making it `std::cout << "________________" << std::flush;` to explicitly flush won't *hurt* anything. – ShadowRanger Jan 08 '21 at 19:35
0

Passing exact number of characters in buffer will do the trick:

std::cin.ignore(std::cin.rdbuf()->in_avail());

StahlRat
  • 1,199
  • 13
  • 25