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
  • 22,446
  • 24
  • 118
  • 241
  • 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

2

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
  • 18,520
  • 6
  • 30
  • 51
  • 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
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
  • 59,916
  • 5
  • 82
  • 126