4
votes

Problem:

My program is trying to mix 'std::cin << ...' and std::getline, with a dash of 'select'. It has odd behavior, but is not the typical "you need std::cin.ignore" situation.

Desired Operation:

The intention is to read tokens from std::cin (if any) until the token 'stop' is encountered. Whatever remains in std::cin is read later as a response to a question. For example:

$ echo 'one two stop y' | ./a.out
read 'one'
read 'two'
Question? y
read 'y'
$

The 'one' and 'two' tokens are read, then 'stop' is encountered which prevents the 'y' being read. Only when the question is asked, the 'y' should be read.

Actual Operation:

read 'one two stop y'
Question? read ''
Question? read ''
Question? read ''
...

The program calls 'select' first, to check whether there are tokens waiting, then it uses std::cin, then std::getline. Ordinarily this is where you might think I need to read that pending '\n' that std::getline doesn't read. But the select does not indicate that there is any input waiting, so std::cin misses it, and std::getline gets all of it.

However, if a timeout is added, it works perfectly:

  tv.tv_usec = 1000;

Finally, a Question:

What is going on? And what silly mistakes have I made?

Code:

#include <iostream>
#include <sys/select.h>

int main (int argc, char* argv[])
{
  // Step 1, read until 'stop'.
  struct timeval tv;
  tv.tv_sec = 0;
  tv.tv_usec = 0;

  fd_set fds;
  FD_ZERO (&fds);
  FD_SET (STDIN_FILENO, &fds);

  int result = select (STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
  if (result && result != -1)
  {
    if (FD_ISSET (0, &fds))
    {
      std::string token;
      while (std::cin >> token)
      {
        if (token == "stop")
          break;

        std::cout << "read '" << token << "'\n";
      }
    }
  }

  // Step 2, ask a question.
  std::string answer;
  do
  {
    std::cout << "Question? ";
    std::getline (std::cin, answer);
    std::cout << "read '" << answer << "'\n";
  }
  while (answer != "y");

  return 0;
}
2
cant you add the predicted expected output and actual output for a set of inputs?Rohit Vipin Mathews

2 Answers

3
votes

If you have tv.tv_usec = 0 then it is just polling to see if something is there. Something has to exist for the file statement to return something other than 0 (which indicates a the time was reached with no inputs available).

The other issue I observe is that with tv.tv_usec and tv.tv_sec = 0, you are only checking once to see if there is data available to be processed. If the input FD is not set when you reach the select, it will just fall through (result is zero) and then exit the program. I suspect that setting some time in tv, gives you an opportunity to type something which would set the descriptor. I would put the select inside a loop to wait for input to occur. The way the program is currently written, the select will only check once to see if there is input. I have an example of the loop below.

while ( 1 ) 
{
    FD_ZERO (&fds);   
    FD_SET (STDIN_FILENO, &fds);
    tv.tv_sec = 0; 
    tv.tv_usec = 100;
    result = select (STDIN_FILENO + 1, &fds, NULL, NULL, &tv);
    // Code here to read in line if result is not zero

}

I would put some time to wait in as well, or you will just be spinning through the loop very fast. Select will return when there is input and tv will contain the time remaining.

1
votes

Most problems of mixing cin and cin.getline are results or side effects of Flushing problem. i dont think it will be any different in this scenario also.

EDIT: And yes you have to use peek to check for a character with out extracting it from the stream.