9

I am a student of C++. I am working through the book, "Starting Out With C++ Early Objects (9th Edition). Example 27 from Chapter 6 (on Functions) reads data from a file but will not compile. Here is the full code:

// Program 6-27
#include <iostream>
#include <string>
#include <fstream>
#include <iomanip>
using namespace std;

// Function prototype
bool readData(ifstream &someFile, string &city, double &rain);

int main()
{
    ifstream inputFile;
    string city;
    double inchesOfRain;

    // Display table headings
    cout << "July Rainfall Totals for Selected Cities \n\n";
    cout << " City      Inches \n";
    cout << "_________________ \n";

    // Open the data file
    inputFile.open("rainfall.dat");
    if (inputFile.fail())
    cout << "Error opening data file.\n";
    else
    {
        // Call the readData function
        // Execute the loop as long as it found and read data
        while (readData(inputFile, city, inchesOfRain) == true)
        {
            cout << setw(11) << left << city;
            cout << fixed << showpoint << setprecision(2)
                << inchesOfRain << endl;
        }
        inputFile.close();
    }
    return 0;
}

bool readData(ifstream &someFile, string &city, double &rain)
{
    bool foundData = someFile >> city >> rain;
    return foundData;
}

And here's the accompanying data for the data file Rainfall.dat:

Chicago 3.70
Tampa 6.49
Houston 3.80

The problem lies with this line in the "bool readData" function:

bool foundData = someFile >> city >> rain;

I am using Visual Studio Community 2017. "someFile" gets a red squiggly line and the dropdown displays the following error:

no suitable conversion function from "std::basic_istream<char, std::char_traits<char>>" to "bool" exists

I don't really understand the error message but have managed to get this program working with:

A simple cast:

bool readData(ifstream &someFile, string &city, double &rain)
{
    return static_cast<bool>(someFile >> city >> rain);
}

Or this as an alternative:

bool readData(ifstream &someFile, string &city, double &rain)
{
    if(someFile >> city >> rain)
        return true;
    else
        return false;
}

So, my real questions are:

  • Are my solutions ok or is there a better way?
  • why is an error being thrown at all on Educational material that you would imagine should have been thoroughly tested first. Or is this just Visual Studio (intelliSense) specific, but works just fine on other compilers?
  • 1
    Post error messages as verbatim text instead of images please! – user0042 Oct 10 '17 at 22:51
  • Just use `return someFile >> city >> rain;` instead of this redundant `if() else` construct. There's no need for a `static_cast`. – user0042 Oct 10 '17 at 22:52
  • 1
    Thanks. I actually tried return someFile >> city >> rain; first but still got the red squiggly. Only the cast removes it! – Webbmaster1 Oct 10 '17 at 22:57
  • You might want to try [a more recent book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – nwp Oct 10 '17 at 23:06
  • 1
    Off topic - but I find myself wondering why `readData` requires an `ifstream` instead of accepting any `istream` (which would be better if down the road you wanted to be able to read from `cin` or from an `istringstream` instead). Then, as one of the answers hinted, I would tend to have it return `istream&` instead of the `bool`. – Daniel Schepler Oct 11 '17 at 00:21

4 Answers4

8

I'd consider

  • returning std::ios& to postpone the contextual conversion to bool

    std::ios& readData(std::ifstream &someFile, std::string &city, double &rain) {
        return someFile >> city >> rain;
    }
    

    The upshot is that you can simple use it like so down-the-road:

    if (readData(file, city, rain)) {
        // ...
    }
    

    The interface will compile with just including #include <iosfwd>


  • manually triggering the contextual conversion:

    bool readData(std::ifstream &someFile, std::string &city, double &rain) {
        return bool{someFile >> city >> rain};
    }
    
sehe
  • 374,641
  • 47
  • 450
  • 633
6

The stream has a member

explicit operator bool() const;

which makes it convertible to a bool value, but because the operator is explicit this only works in a context that requires a bool.

You have already discovered that this includes if-statements and explicit casts. It doesn't include other types of expressions, like assignments.

Originally (C++98) the operator wasn't explicit (because such things weren't invented yet) so the code example would probably have worked at the time. Seems like the book hasn't been updated in this part.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • Doesn't really answer, why visual-studio-2017 intellisense rendering complains. – user0042 Oct 10 '17 at 23:01
  • @user0042 Why not? The code is not valid C++11 or whatever the standard is that visual studio 2017 is trying to implement, so intellisense putting red squigglies under the code is what is supposed to happen. – nwp Oct 10 '17 at 23:05
  • Yep that was my question 2. Did the author use an alternative non complaining compiler? – Webbmaster1 Oct 10 '17 at 23:06
  • @nwp Doesn't the return type _require a `bool`_? – user0042 Oct 10 '17 at 23:07
  • @Bo Yes, I also thought so. The intellisense parser doesn't follow the currently implemented standards. Often seen that with VS. – user0042 Oct 10 '17 at 23:08
  • @user0042 It does, but apparently [it is still considered an implicit conversion](http://coliru.stacked-crooked.com/a/b8d2231e53db2037). – nwp Oct 10 '17 at 23:08
  • 1
    Everyone. FYI. This book is apparently bang up to date published 2017 and is supposed to be C++11 compliant. – Webbmaster1 Oct 10 '17 at 23:10
  • 1
    @Webbmaster1 Well, [all the](http://coliru.stacked-crooked.com/a/557a60ed2e3f87bb) major [compilers](http://coliru.stacked-crooked.com/a/b8d2231e53db2037) disagree [with the](http://rextester.com/FZA94572) book. You could search for an errata for that book. – nwp Oct 10 '17 at 23:15
  • 1
    @Webbmaster1 - There are bugs in books as well. Not just in programs. – Bo Persson Oct 10 '17 at 23:16
3

The operator bool for streams is declared in the base class basic_ios the following way

explicit operator bool() const;
^^^^^^^^

So there is no implicit conversion from the std::basic_ifstream to the type bool.

This solution

bool readData(ifstream &someFile, string &city, double &rain)
{
    return static_cast<bool>(someFile >> city >> rain);
}

looks good.

You can also use a trick with the logical negation operator the following way

bool readData(ifstream &someFile, string &city, double &rain)
{
    return !!(someFile >> city >> rain);
}

because according to the C++ Standard (5.3.1 Unary operators)

9 The operand of the logical negation operator ! is contextually converted to bool (Clause 4); its value is true if the converted operand is false and false otherwise. The type of the result is bool.

Though in my opinion the first function implementation is more readable.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • MSVC doesn't even support standards previous to C++14 any more, so one might just as well make good use of C++14 features. So what about changing the return type specificer of ``readData`` to ``decltype(auto)``? (The downside is of course that such a function cannot be forward-declared, so it needs to be moved up. Also the ``== true`` must be removed.) – Arne Vogel Oct 11 '17 at 11:25
2

I suppose they don't cover it until later in the book, but I'd approach this somewhat differently. I'd start by defining a struct to hold the name and rainfall for a city together:

struct precipitation { 
    std::string location;
    double amount;
};

Then I'd define an overload of operator>> to extract an object of that type from a stream:

std::istream &operator>>(std::istream &is, precipitation &p) { 
     return is >> p.location >> p.amount;
}

This is pretty much the standard form for stream extractors--take reference to a stream and a reference to an object of the type being extracted, and return the stream (again, by reference).

This lets you read fairly cleanly:

precipitation precip;

std::ifstream in("rainfall.dat");

while (in >> precip)
    std::cout << "City: " << precip.location << ", amount: " << precip.amount << "\n";

In case you're wondering how that works: a stream object supports conversion to Boolean, which produces true when reading from the stream has succeeded, and false when it fails, so this reads from the stream until reading fails, then it stops.

This is also the form expected by stream iterators, so it lets you use them as well. For example, to read the entire contents of the file into a vector, you'd do something like this:

std::vector<precipitation> data { std::istream_iterator<precipitation>(in),{}};
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • 1
    You magically read my mind. I had the word `precipation` in a type in my answer at first! We must have been conjoined twins, separated at birth. Or something. (I removed it because it didn't feel essential to the question. Still +1 for going the educational mile!) – sehe Oct 11 '17 at 00:56