11

The C++ IO streams' base class std::basic_ios defines operator void*() to return !fail() and operator!() to return fail(). That makes me wonder why we need the operator!() at all. Certainly, !is would also work by implicitly calling operator void*() and negating its result.

Am I missing something here or is it purely for historical reasons that std::basic_ios::operator!() is defined?

A question on comp.lang.c++.moderated didn't bring any answers either.

sbi
  • 219,715
  • 46
  • 258
  • 445

3 Answers3

8

With old (read: not long after cfront) C++ compilers, the compiler was not guaranteed to implicitly call typecast operators on objects when needed. If iostream didn't have an operator ! declared, then you couldn't expect !cout to work in all cases. C++89 (or whatever the pre-C++98 standard was called) simply left the area undefined.

This is also why operator void*() was overloaded, and not operator int or operator bool. (bool didn't even exist as its own type in the standard at that point.) I remember my professor telling me that if(), under the hood, expected a void* in C++, because that type could act as a "superset" type relative to those expression result types that would be passed to an if statement, but I have not found this spelled out anywhere.

This was around the time of gcc 2, when most folks didn't support templates or exceptions, or if they did, didn't fully support them, so metaprogramming C++ with templates was still a theoretical exercise and you made sure to check that operator new didn't return a null pointer.

This drove me nuts for several years.

An interesting excerpt from Stroustrup's The C++ Programming Language, 3rd ed. (1997), page 276:

The istream and ostream types rely on a conversion function to enable statements such as

while (cin >> x) cout << x;

The input operation cin>>x returns an istream&. That value is implicitly converted to a value indicating the state of cin. The value can then be tested by while. However, it is typically not a good idea to define an implicit conversion from one type to another in such a way that information is lost in the conversion.

There's a lot in C++ that seems to be a victory of cute or clever over consistent. I wouldn't mind one bit if C++ was smart enough to handle the above loop as:

while (!(cin >> x).fail()) cout << x;

because this, while more verbose and more punctuation, is clearer to a beginning programmer.

... Actually, come to think of it, I don't like either of those constructs. Spell it out:

for(;;)
{   cin >> x;
    if(!cin)
        break;
    cout << x;
}

Why do I like this better? Because this version makes it far clearer how to expand the code to, say, handle two reads at a time instead of one. For example, "The existing code copies a sequence of float values. We want you to change it so it pairs up the float values and writes them out, two per line, because we're now using complex numbers."

But I digress.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Mike DeSimone
  • 41,631
  • 10
  • 72
  • 96
  • So that's another explanation that confirms my suspicion that `operator!()` isn't used for (current) technical reasons. As for the second half of your answer: I consider streams a very ancient design. Things like `good()` not being the opposite of `bad()`, stream buffers performing _both_ buffer management and writing to/reading from an external device, and, well, a unneeded `operator!()` probably wouldn't be accepted by, say boost's peer review nowadays. – sbi Aug 23 '10 at 08:24
  • Yes, it's a legacy thing, though the fact that it's explicit rather than implicit could resolve some ambiguities. (No, I can't think of an example.) And yes, streams is a good example of someone getting clever and making a monster. (Overloading the shift operators? Really?! Don't start me on ...) – Mike DeSimone Aug 23 '10 at 13:10
  • Personally, I'm annoyed with C and C++'s obsession with handling presentation (formatting) and delivery (buffering, file I/O, etc.) in the same function. How many functions are there whose names end in `printf`? Much better is Python's approach, with formatting and output as entirely separate functions (the C++ equivalent would be having a `std::string std::format(pattern, ...)` function). – Mike DeSimone Aug 23 '10 at 13:14
  • Also, it was amusing reading Strostrup--the inventor of the language--writing things like "Every compiler I have tested functions this way." and thinking how the SO commenters will rend him limb from limb for saying that empirical evidence trumps what is written in a standard. – Mike DeSimone Aug 23 '10 at 13:16
  • @Mike: Mayhaps those SO commenters should reflect on the practical implications of that, while remembering the difference between theory and practice is smaller in theory than in practice. – Fred Nurk Jan 06 '11 at 11:27
  • Haven't heard that version of the phrase. I'll remember that. I usually just say that practice pays better. – Mike DeSimone Jan 06 '11 at 13:26
2

Ok, coming up dry here, I went and asked on comp.lang.c++.moderated.

At first, the results were just as bad as they've been here, but in the end Daniel Krügler's answer concurred in my suspicion that there are no technical reasons for operator!():

I have been told, that this additional declaration was added to emphasize the symmetry between the "true" case and it's negation, just as a guide for the reader, nothing more. To be fair, the idiom

operator void*

was rather new at this time and given this the deduction which syntax support is provided by this feature is not immediately obvious. Other than that there was no further technical reason to do so. [...]

Community
  • 1
  • 1
sbi
  • 219,715
  • 46
  • 258
  • 445
  • 3
    Daniel Krügler is easily one of the most helpful people I've ever encountered in an online forum. If only we could get him to join the party here at Stack Overflow... – James McNellis Aug 22 '10 at 18:38
  • 1
    @James: +1 to that. He's like litb++ (no offense, Johannes. <3) – GManNickG Aug 22 '10 at 18:52
  • @James, @GMan: I am old enough to actually remember his last name being Spangenberg in the 90s. `:-/` – sbi Aug 30 '14 at 19:41
1

Looking at the implementation of MinGW, which is shipped with Codeblocks shows me this code:

  operator void*() const
  { return this->fail() ? 0 : const_cast<basic_ios*>(this); }

  bool
  operator!() const
  { return this->fail(); }

It seems to me, that the operator void*() const is intended for more uses than only for the check of success. On top of that it serves also as a casting operator (we're returning this). Right now I am scratching my head a little bit, why we may want to cast this to void*. But the rest is quite clear - if you've got an erroneous stream anyway, you can also return null.

ablaeul
  • 2,750
  • 20
  • 22
  • 3
    +1 But I think this is simply a mechanism to return (in the OK state) a valid pointer and not possibly some trap value. –  Jul 11 '10 at 19:47
  • 1
    @Neil: Yes, I think so, too. The standard says about `operator void*()` (in 27.4.4.3/1): "Returns: If `fail()` then a null pointer; otherwise some non-null pointer to indicate success." That's just a safe bool returning `!fail()`. About `operator!()` it says (in 27.4.4.3/2): "Returns: `fail()`." – sbi Jul 12 '10 at 15:42