I/O from cin or ifstream

I want to do input from std::cin, or from the file specified in argv[1]. How
do I do this in idiomatic C++?

Here's what I want to do in C:

1
2
3
4
5
6
7
8

int main(int argc, char *argv[])
{
  FILE *f = stdin;
  if (argc == 2) f = fopen(argv[1], "r");
  if (!f) handleError();
  // do something with f
}


Here's what I've got in C++. I actually want to construct an object foo,
which is of class Foo. The code below works (if I didn't make a typo) but
it requires foo to be constructed first with the default constructor. Is
there some more idiomatic way to do this in C++?

1
2
3
4
5
6
7
8
9
10
11
int main(int argc, char *argv[]) {
  Foo foo;
  if (argc == 2) {
    std::ifstream f(argv[1]);
    if (!f) handleError();
    foo = Foo(f);
  } else {
    foo = Foo(std::cin);
  }
  // do something with foo
}

I think the code you wrote is perfectly acceptable, but if it's for some reason desirable to avoid the default construction, you could do
1
2
3
4
5
6
7
8
9
std::istream *stream = &std::cin;
std::ifstream file;
if (argc >= 2){
    file.open(argv[1]);
    if (!file)
        //handle it
    stream = &file;
}
Foo foo(*stream);

Another alternative could be to make foo an std::unique_ptr<Foo>.
Last edited on
As another alternative, you might do something like this.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <fstream>
#include <string>
#include <exception>

class Foo
{
    std::ifstream ifs;
    std::istream& in;
public:
    Foo(const std::string& filename)
        : ifs(filename),
          in(ifs ? ifs : std::cin)
    {
        if (!filename.empty() && !ifs)
            throw std::invalid_argument("cannot open input file");
    }
    void print_line()
    {
        std::string line;
        std::getline(in, line);
        std::cout << '[' << line << "]\n";
    }
};

int main(int argc, char **argv)
{
    try
    {
        Foo foo(argc >= 2 ? argv[1] : "");
        foo.print_line();
    }
    catch (std::invalid_argument& e)
    {
        std::cout << "error: " << e.what() << '\n';
    }
}

Last edited on
I was thinking something similar, another over-engineered solution :)
(I like helios' solution)

The point here is that it hides the actual need for the end user to have a separate ifstream object. The user just needs to care about the stream, and RAII takes care of the ifstream.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <fstream>

struct InStream {
    
    std::ifstream* pFile = nullptr;
    std::istream& stream;
    
    InStream(const char* filename_or_null)
    : stream(init_stream(filename_or_null)) { }

    ~InStream()
    {
        delete pFile;   
    }

  private:
    std::istream& init_stream(const char* filename)
    {
        if (filename != nullptr)
        {
            pFile = new std::ifstream(filename);
            if (!pFile->is_open())
            {
                // handle error, throw, or let user handle error
            }
            return *pFile;
        }
        else
        {
            return std::cin;   
        }
    }
};

int main(int argc, char *argv[]) {
    
    InStream setup((argc == 2) ? argv[1] : nullptr);
    std::istream& f = setup.stream; 

    // use f regardless of it's a file or stdin
    int oolith;
    if (f >> oolith)
    {
        std::cout << oolith << '\n';
    }
    else
    {
        std::cout << "error\n";
    }
}


It's a shame that you can't implicitly use a conversion operator like
1
2
3
4
operator std::istream&()
{
    return stream;
}

or I wouldn't need line 39.
Last edited on
If it's about over-engineering...
(Don't do this, OP. I'm just fucking around.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class IStreamWrapper{
public:
    virtual ~IStreamWrapper(){}
    virtual std::istream &operator*() = 0;
    static std::unique_ptr<IStreamWrapper> create(const std::vector<std::string> &);
};

class CoutWrapper : public IStreamWrapper{
public:
    std::istream &operator*() override{
        return std::cout;
    }
};

class IFstreamWrapper : public IStreamWrapper{
    std::ifstream file;
public:
    IFstreamWrapper(const std::string &path): file(path){
        if (!this->file)
            throw std::runtime_error("file not found");
    }
    std::istream &operator*() override{
        return this->file;
    }
};

std::unique_ptr<IStreamWrapper> IStreamWrapper::create(const std::vector<std::string> &args){
    if (args.size() < 2)
        return std::make_unique<CoutWrapper>();
    return std::make_unique<IFstreamWrapper>(args[1]);
}

std::vector<std::string> to_vector(int argc, char **argv){
    std::vector<std::string> ret;
    ret.reserve(argc);
    for (int i = 0; i < argc; i++)
        ret.emplace_back(argv[i]);
    return ret;
}

int main(int argc, char **argv){
    auto args = to_vector(argc, argv);
    auto wrapper = IStreamWrapper::create(args);
    Foo foo(**wrapper);
    //...
}
Last edited on
IStreamWrapper
Ah yes, the interface class for a StreamWrapper.
> I actually want to construct an object foo, which is of class Foo.

Construct it with a (pointer to) the stream buffer that it should use.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <fstream>

struct Foo
{
     // use the streambuffer as the associated stream buffer of the input stream
     explicit Foo( std::streambuf* stmbuf ) : stm(stmbuf) {}

     std::istream stm ; // read input from this input stream

     // ...
};

int main( int argc, char* argv[] )
{
    static std::filebuf fbuf ;

    if( argc == 2 ) fbuf.open( argv[1], std::ios::in ) ; // try to open the file for input

    // if the file was successfully opened, use the file buffer, otherwise use stdin's buffer
    Foo foo( fbuf.is_open() ? std::addressof(fbuf) : std::cin.rdbuf() ) ;

    // ...
}
Neat. Question: If argc is not 2, once that stm object is destroyed, is it illegal to use std::cin?
The type of the stream stm is std::istream; its destructor would not destroy the associated streambuffer.

This destructor does not perform any operation on the underlying streambuffer (rdbuf()): the destructors of the derived input streams such as std::basic_ifstream and std::basic_istringstream are responsible for calling the destructors of the streambuffers.
https://en.cppreference.com/w/cpp/io/basic_istream/~basic_istream
Thanks! I didn't see that.
Thank you everyone for your replies. They have given me much food for thought. I really appreciated how we all seemed to be on the same wavelength, responding to issues that we didn't even discuss.

First of all, in my original C++ implementation I was bothered by the wasted construction of foo. It wasn't just that, but why should I have to add a default constructor to Foo just because the caller can't figure out how to specify a proper istream. Also, the implementation of Foo is elegant. It doesn't care where the istream comes from--it just uses it. So I didn't want to put any of the {file,standard input} code in Foo. Who knows: I might even want to initialize it using a stringstream.

So helios's original solution makes a lot of sense. I write a lot of little filter programs, and I've used the paradigm in my C code quite a few times. His solution is very close to a transliteration of that C code. I could easily see using it in a bunch of little C++ programs. The only drawback (and it is really tiny) is that there is a temporary variable whose scope doesn't end when the variable is no longer needed. I think that this unspoken defect was the driver for all the (overengineered :-)) solutions which I am still studying.

JLBorges solution was great, because it enabled me to learn more about C++ I/O. In fact, in my current project, I may just use the streambuf pointer directly to construct foo. I only use std::istream:read. I could replace those occurrences with std::streambuf::sgetn.
Topic archived. No new replies allowed.