Using istream_iterator and reading from standard input or file
Asked Answered
G

4

7

I'm writing in Microsoft Visual C++ and I'd like my program to either read from standard input or a file using the istream_iterator. Googling the internets hasn't shown how simple I think it must be. So for example, I can write this pretty easily and read from standard input:

#include <iostream>
#include <string>
#include <iterator>

using namespace std;

int main()
{
   istream_iterator<string> my_it(cin);
   for (; my_it != istream_iterator<string>(); my_it++)
      printf("%s\n", (*my_it).c_str());
}

Or I can write this and read from a file:

#include <iostream>
#include <string>
#include <iterator>
#include <fstream>

using namespace std;

int main(int argc, char** argv)
{
   ifstream file(argv[1]);
   istream_iterator<string> my_it(file);
   for (; my_it != istream_iterator<string>(); my_it++)
      printf("%s\n", (*my_it).c_str());
}

But how do I combine these two so that a simple (argc == 2) check lets me initialize my input stream iterator with either a file stream or stdin and go on about my merry way?

Goodwin answered 16/12, 2009 at 2:4 Comment(0)
E
14

You can assign to the iterator after constructing it:

int main(int argc, char** argv)
{
   ifstream file;
   istream_iterator<string> my_it;

   if(argc == 2) {
      file.open(argv[1]);
      my_it = istream_iterator<string>(file);
   } else {
      my_it = istream_iterator<string>(cin);
   }
}
Exeter answered 16/12, 2009 at 2:26 Comment(0)
C
1

This small snippet will give you an istream input that can be either a file or std::cin.

std::ifstream filestream;
if ( use_file )
    filestream.open( ... );
std::istream &input = use_file ? filestream : std::cin;

You may now use input without worrying which source the input is coming from.

Chatoyant answered 16/12, 2009 at 2:25 Comment(3)
This attempts to bind an r-value to a non-const reference, and is illegal in C++.Foreshank
@Alex: If one operand can be implicitly converted to a reference to the other type, the result is an lvalue. open-std.org/jtc1/sc22/wg21/docs/wp/html/nov97-2/…Chatoyant
You seem to be correct -- VC 2008 compiles this with no issue. Looks like a VC 2005 bug.Foreshank
P
1

At first glance, the simplest solution would be to use the ternary operator ?: like this:

istream_iterator<string> my_it( (argc == 2) ? ifstream(argv[1]) : cin );

However, that won't quite work because it constructs a temporary ifstream object, which will be destroyed at the end of the statement. So you need a way of conditionally creating an ifstream, and conditionally destroying it after the for loop. std::auto_ptr<> fits the bill. Thus:

auto_ptr<ifstream> file((argc == 2) ? new ifstream(argv[1]) : NULL);
istream_iterator<string> my_it( (argc == 2) : *file : cin);
for (; my_it != istream_iterator<string>(); my_it++)
   printf("%s\n", (*my_it).c_str());

A different, probably cleaner solution would be to move the iteration to a separate function that takes istream&.

I've seen this problem before online, covered by one of the C++ greats. Unfortunately, I don't remember exactly where, or by whom! I think it was on DDJ, maybe Sutter or Alexandrescu?

Polyphone answered 16/12, 2009 at 2:29 Comment(1)
This also attempts to bind an rvalue to a non-const reference. VC++ says: A non-const reference may only be bound to an lvalueForeshank
P
0

Do you mean something like this: (using pointers)

#include <iostream>
#include <string>
#include <iterator>
#include <fstream>

using namespace std;

int main(int argc, char** argv)
{
   istream_iterator<string>* my_it = NULL;
   if (argc == 2)
   {
       ifstream file(argv[1]);
       my_it = new istream_iterator<string>(file);
   }
   else
   {
       my_it = new istream_iterator<string>(cin);
   }

   ...

   delete my_it;
}

I haven't tested this, though. Is that sort of what you're after?

Peppard answered 16/12, 2009 at 2:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.