1

I would like to parse either an stdin stream or a file. So I want a function/method to accept either of this.

Note that my goal is not to call read twice!

As istream is the base class for cin and ifstream` I should be able write this:

#include <iostream>
#include <fstream>

void read(std::istream &fp) {
    while(!fp.eof()) {
        std::string line;
        std::getline(fp, line);
        std::cout << line << std::endl;;
    }
}

int main(int argc, char *argv[])
{
    std::ifstream fp;

    if (argc >= 2) {
        fp.open(argv[1]);
        if (!fp) abort();
    }
    else {
        fp = std::cin;
    }
    
    read(fp);

    if (fp.is_open()) {
        fp.close();
    }
    return 0;
}

In C I can do the following with calling it with either read_content(stdin) or read_content(fp):

void read_content(FILE *file)

What is the proper way to do this in C++?

nowox
  • 25,978
  • 39
  • 143
  • 293
  • 2
    Either initialize an `std::istream &` conditionally or use a `std::istream *` instead. (References may not be assigned but pointers can.) – Scheff's Cat Dec 02 '20 at 11:24
  • Why not just `read(std::cin)` or `read(fp)`? There's not a lot of code saved by recycling `fp`. – tadman Dec 02 '20 at 11:26
  • 1
    Just a note - you didn't say what actual problem you had with your existing code. I can guess, and I can reproduce it myself if I want, but in general it is helpful to actually paste whatever compile errors you get in the question. – Useless Dec 02 '20 at 11:27
  • 1
    `while(!fp.eof())` [Why is while feof always wrong](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – KamilCuk Dec 02 '20 at 11:43

2 Answers2

3

std::cin is an instance of std::istream and not derived from std::ifstream but the opposite is true.

Inheritance graph of Stream-based I/O:

Inheritance graph of Stream-based I/O

(Taken from cppreference.com - Input/output library)

So, OPs intention can be performed using a reference or pointer to std::istream.

Demo:

#include <iostream>
#include <fstream>

void read(std::istream &fp) {
    while(!fp.eof()) {
        std::string line;
        std::getline(fp, line);
        std::cout << line << std::endl;;
    }
}

int main(int argc, char *argv[])
{
    std::ifstream fp;
    std::istream &in = (argc >= 2)
      ? [&]() -> std::istream& {
        fp.open(argv[1]);
        if (!fp) abort();
        return fp;
      }()
      : std::cin;
    
    read(in);

    if (fp.is_open()) {
        fp.close();
    }
    return 0;
}

Compiled on coliru


Note:

    while (!fp.eof()) {

should be replaced by

    while (fp) {

The reason for this has been thoroughly discussed in
SO: Why is iostream::eof inside a loop condition (i.e. while (!stream.eof())) considered wrong?.

Scheff's Cat
  • 19,528
  • 6
  • 28
  • 56
  • I didn't know it was possible to pass lambdas on ternary operators :) – nowox Dec 02 '20 at 11:30
  • 1
    @nowox I once discovered that immediately called lambdas are good to turn any sequence of statements into an expression. (That's useful e.g. to inline things into a member init. of constructor or in an `assert()`.) It looks a bit strange but over time you get used to... ;-) – Scheff's Cat Dec 02 '20 at 11:32
  • Is the explicit closing of `fp` necessary? This answer seems to suggest otherwise: https://stackoverflow.com/a/748059/7024231 – user1234 Dec 14 '22 at 19:14
  • 1
    @user1234 On one hand, an `fstream` object will be closed in the destructor if necessary. On the other hand, a close may require flushing of the pending buffer. If the latter fails (say, the hard drive is full or the network connection lost in exact this moment) you can check (and handle) this after close. If close is done in destruction there is no chance to do so because afterwards there is nothing left to check. (It's similar if you use `std::fstream::exception(std::ios::badbit | std::ios::failbit)`: `close()` may throw but the destructor doesn't regardless whether it failed or not.) – Scheff's Cat Dec 15 '22 at 06:55
  • 1
    @user1234 Though, I must admit there isn't any error checking in the MCVE above... ;-) – Scheff's Cat Dec 15 '22 at 06:56
1

In C I can do the following with calling it with either read_content(stdin) or read_content(fp):

Yes, and in C++ you should just call either read(std::cin) or read(fp). It's exactly the same.

The line

fp = std::cin;

is the wrong thing to do, as std::cin is only declared as a std::istream. There is no std::ifstream constructor taking an istream, you don't want an independent object here anyway, and if std::cin is really an object of some type derived from std::ifstream, you'd slice it.

Useless
  • 64,155
  • 6
  • 88
  • 132