istream.operator>> == whyDidYouDoThis

Started by
4 comments, last by SiCrane 10 years, 7 months ago

So why is it that the majority of the time I want cin to behave a certain way, and I think a lot of other people probably want it to behave that way as well. The thing that's bothering me is that, not only is it implemented differently, but there's not even stream manipulators to make it behave the way people want it to.

What am I talking about?

Well...

I wrote this:


template <typename T>
T getUserValue(const string& requestString) {
  T retval;
  while(true) {
    cout << requestString;
    cin >> retval;

    bool ok = cin.good();
    cin.clear();
    cin.ignore(numeric_limits<streamsize>::max(), '\n'); //whyyyyyy

    if(ok) {return retval;}
    cout << "Invalid input." << endl;
  }
}

Ignoring the annoyance of cin.ignore to discard the newline character, this works just fine for grabbing ints and floats. It works just fine for grabbing a std::string, UNLESS the string has a space in it - and there's my main problem.

So what I want to know is...

Why aren't there manipulators that solve these common problems? Why can't I just say:


template <typename T>
T getUserValue(const string& requestString) {
  T retval;
  while(true) {
    cout << requestString;
    cin >> allow_spaces >> consume_eol >> retval;
    if(cin.good()) {return retval;}
    cin.clear();
    cout << "Invalid input." << endl;
  }
}

Now I'm not asking you to tell me about std::getline(). I understand that I can get lines that way. I'm just wondering why cin is so clumsy, having all these pitfalls in the face of common, expected usage, and doesn't have the obvious fixes available to it.

Am I missing something here?

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
Advertisement
To be honest, in 15 years using the language, I've never seen C++ streams used in production code.

I think the folks who standardized them have regrets about their decisions too...

Now I'm not asking you to tell me about std::getline(). I understand that I can get lines that way. I'm just wondering why cin is so clumsy, having all these pitfalls in the face of common, expected usage, and doesn't have the obvious fixes available to it.

Am I missing something here?

I concur using formatted streams can be very clumsy, but to be honest a lot of people seem to be using them for parsing which is a rather complicated task (depending on the grammar and how you go about it). They have some limited parsing functionality, but to do any real parsing you pretty much always have to either make your own parser or use an existing one.

There are some pros and cons to this. It'd be nice if they added more functionality, and some certainly could be added reasonably (that could be said about a lot of parts of C++, even after C++11), but for the most part if they had included more "nice" functionality they would have been sitting on that awkward boundary between minimal parser and a full blown parser (at least a regular parser if not a context free parser). IMO I'd prefer not to have such an awkward parser standardized -- and as for doing a full blown parser* you'd probably want a completely different interface than what C++ streams expose.

*(either regular or context free; I think context sensitive and certainly unrestricted languages would be too complex for a standardized parser)

In short, C++ streams are one of those ancient artifacts that we're left with that we typically try to use for more complicated tasks than they were designed for. Kind of an unfortunate situation, really. There are a few things that could be added that I think might be a good idea*, but I've seen a lot of people suggest ideas that are a bit too big -- out of the scope of C++ streams and into the realm of needing a real parser.

*The C++ committee has a lot of work to do; I'm not sure if these small functionalities are on the top of their priority list. And like Hodgman suggests, we've learned a lot since C++ was first standardized in '98 and perhaps some unfortunate decisions were made; truly "fixing" things might require some breaking of the existing standard library API.

[size=2][ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

I'd be happy if I could just get a manipulator that makes cin.operator>>() work like std::getline() when it's used for a string.

*Sad Panda*

Maybe I'll just try to write one and see where I can go from there.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.
In his book The Design and Evolution of C++, Stroustrup wrote that he was trying to get the same or similar effect as the shell << and >> operations that moved data from place to place, and also that he was never very satisfied with it.

It is nice as a convenience for quick-and-simple toy apps, and for students learning to program. It isn't frequently encountered in larger works.
For the question of why the standards committee doesn't introduce new manipulators I've heard three explanations. 1) Manipulators that alter stream behavior would make it much too easy to break existing code. For instance, if you introduce a manipulator that automatically eats any trailing newlines after an extraction, then existing code that manually takes care of those newlines would start improperly parsing input if that manipulator is set on a stream. 2) The stream I/O section of the standard currently defines a lot of what should be implementation details, so introducing new manipulators would involve basically rewriting the entire formatted I/O section. 3) If they added one new manipulator, then people would start asking for more and more manipulators and no one wants to open that particular can of worms. Note that these aren't mutually exclusive, so more than one may be the case or may apply to different degrees to different members of the committee. I don't know if any of these are actually true, but these are what I've heard.

As for your specific issues, it's not hard to write manipulators and wrappers to simplify your code:

std::istream & eat_line(std::istream & is) {
  is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
  return is;
}

class WholeLine {
  public:
    WholeLine(std::string & str) : str_(str) {}
  private:
    std::string & str_;
    friend std::istream & operator>>(std::istream &, WholeLine);
};

std::istream & operator>>(std::istream & lhs, WholeLine rhs) {
  std::getline(lhs, rhs.str_);
  return lhs;
}

int main(int, char **) {
  std::stringstream sstr("13\nfred and barney\nbetty and wilma");
  int number;
  std::string husbands, wives;
  sstr >> number >> eat_line >> WholeLine(husbands) >> WholeLine(wives);
  std::cout << number << std::endl
            << husbands << std::endl
            << wives << std::endl;

  return 0;
}

This topic is closed to new replies.

Advertisement