16

I was trying a test program on failures of opening a file using ifstream. The code is below :-

#include <iostream>
#include <fstream>
#include <type_traits>
using namespace std;
int main()
{
    ifstream ifs ("wrong_filename.txt");
    cout << boolalpha;
    cout << is_pointer<decltype(ifs)>::value <<"\n";
    cout << (ifs==nullptr);
    return 0;
}

Output is :-

false
true

If ifs is not a pointer, then how does it equal to nullptr?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Anwesha
  • 728
  • 1
  • 7
  • 18

1 Answers1

24

Until C++11, C++ streams are implicitly convertible to void*. The result will be NULL if the stream is not in an errorless state and something else if it is. So ifs == NULL (should not work with nullptr, see below) will find and use that conversion, and since your filename was wrong, the comparison will yield true.

In C++11, this was changed to an explicit conversion to bool, with false indicating an error and true a good stream, because the void* conversion allowed too much nonsensical code, such as your example. Indeed, a current compiler in C++11 or C++14 mode will reject your snippet, live. Since your code is obviously at least C++11, your compiler is non-conforming by accepting it.

Those conversions allow and are intended for error checks like this:

if ( !(ifs >> data) )
    std::cout << "Reading data failed.";

or, analogous to your example:

std::ifstream ifs ("wrong_filename.txt");
if (!ifs)
    std::cout << "Could not open file.";

Fun fact of the day: You can also use this to cleanly loop over a file, e.g.:

for (std::string line; std::getline(ifs, line);) {
    // Process line
}
Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
  • 1
    @CppNITR you need a *boolean context*, that is a context which triggers explicit boolean conversions. For example, a cast to `bool`, an `if` condition, or the second part of a `for` header. – Quentin Oct 17 '15 at 13:50
  • @CppNITR Because the `bool` conversion is `explicit`. Good catch, updated the answer, thanks. – Baum mit Augen Oct 17 '15 at 13:51
  • casting to "bool" also fails – DevInd Oct 17 '15 at 13:52
  • 1
    @CppNITR Works [here](http://coliru.stacked-crooked.com/a/73b1913dc8feecae), and should work on every conforming implementation. – Baum mit Augen Oct 17 '15 at 13:52
  • 2
    _"Since your code is obviously at least C++11, your compiler is non-conforming by accepting it."_ Probably using the libstdc++ from GCC 4.x, which supported C++11 features such as `nullptr` but we didn't replace the `operator void*` with `explicit operator bool` until GCC 5. – Jonathan Wakely Oct 17 '15 at 13:57
  • 1
    @hvd, really?! G++ doesn't allow it, but Clang and EDG do. – Jonathan Wakely Oct 17 '15 at 13:59
  • @JonathanWakely http://stackoverflow.com/questions/27573928/are-explicit-conversion-operators-allowed-in-braced-initializer-lists Nvm, that link is for class types, there might be a difference. – Baum mit Augen Oct 17 '15 at 14:02
  • 1
    @hvd, I think clang is correct to reject it for class types, but correct to accept it for `bool`, and GCC is wrong. See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51553 and http://llvm.org/bugs/show_bug.cgi?id=17376 where Richard quotes [over.best.ics]p4 – Jonathan Wakely Oct 17 '15 at 14:07
  • @JonathanWakely Seems like I've been getting the wrong picture in my head, with a somewhat warped interpretation of the standard to support GCC's behaviour. A quick re-read so far does indeed not appear to support the rules as I had remembered them. Will need to re-read in more detail later, thanks for letting me know. –  Oct 17 '15 at 14:18