How to write a class that can be written out to ostream_iterator and read from istream_iterator?

I'm reading C++ Primer, Lippman, the chapter on stream iterators and in particular on istream and ostream iterators.
For istream iterators:
Lippman wrote:
An istream_iterator uses >> to read a stream. Therefore, the type that an istream_iterator reads must have an input operator defined.

For ostream iterators:
Lippman wrote:
ostream_iterator can be defined for any type that has an output operator (the << operator).

ostream iterators can be bound to a variety of streams: file streams (fstream), string streams (stringstream), network streams (boost::asio::ip::tcp::iostream).

One can write to a variety of streams so, for a given object Foo that we wish to write to a file, a string, or network stream -- how do we define operator<< and operator>> so the object can be put on each stream and read from each stream?

I think I'm suppose to overload this operator definition:

ostream& operator<<(ostream& os, const Foo &foo) { ... }

But wouldn't I need to do a dynamic_cast on ostream to figure out what kind of stream it is to take the appropriate action? That is:

1
2
3
4
5
6
7
8
9
10
11
ostream& operator<<(ostream &os, const Foo &foo)
{
   fstream *fs_iter = dynamic_cast<fstream*>(&os);
   if (fs_iter) {
      // Prepare Foo object to be written out to fstream e.g., serialization.
   }

   // Similar for stringstream

   // Similar for network stream
}
Last edited on
From my understanding, the point of overloading <</>> on std::ostream/std::istream is that you shouldn't care about what type of stream it is. You just send information to it. If you need to do special stuff for a file stream vs. a stringstream, then perhaps you shouldn't use operator overloading because too much overloading can be confusing.

That being said, one alternative to dynamic casts would be to overload on the derived type.
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
// Example program
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>

struct Foo { };

std::ofstream& operator<<(std::ofstream &os, const Foo &foo)
{
    std::cout << "Pretend-writing to a file stream!\n";
    return os;
}

std::ostringstream& operator<<(std::ostringstream &os, const Foo &foo)
{
    std::cout << "Pretend-writing to a string stream!\n";
    return os;
}

std::ostream& operator<<(std::ostream &os, const Foo &foo)
{
    std::cout << "Pretend-writing to some other kind of stream!\n";
    return os;
}

int main()
{
    Foo foo;

    std::ofstream fout;
    fout << foo << '\n';
    
    std::ostringstream oss;
    oss << foo << '\n';
         
    std::cout << foo << '\n';
}


The compiler will prefer to overload on the derived type.
Pretend-writing to a file stream!
Pretend-writing to a string stream!
Pretend-writing to some other kind of stream!
Last edited on
> One can write to a variety of streams so, for a given object Foo
> that we wish to write to a file, a string, or network stream --
> how do we define operator<< and operator>> so the object can be
> put on each stream and read from each stream?

The streams are responsible for parsing and formatting input/output text. A stream has an associated stream buffer; this is used by the stream to for the actual transport of characters from/to an external device. The protected virtual interface of the stream buffer class provides polymorphic operations that are used to send/receive characters to/from the external device.
https://en.cppreference.com/w/cpp/io/basic_streambuf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <sstream>
#include <string>
#include <fstream>

int main()
{
    std::ostream stm( std::cerr.rdbuf() ) ; // output stream; initially uses the stream buffer of std::cerr
    stm << "hello world!" << '\n' << 1234 << '\n' ; // output to stderr

    std::filebuf fbuf ; // file buffer
    fbuf.open( "output.txt", std::ios::out ) ;
    stm.rdbuf( std::addressof(fbuf) ) ; // the stream now uses the file buffer
    stm << "hello world!" << '\n' << 1234 << '\n' ; // output to file output.txt

    std::stringbuf strbuf( std::ios::out ) ; // string buffer
    stm.rdbuf( std::addressof(strbuf) ) ; // the stream now uses the string buffer
    stm << "hello world!" << '\n' << 1234 << '\n' ; // output to string
}
Folk, appreciate the info but to clarify - I'm asking how to make a class compatible with ostream_iterator and istream_iterator, not about streams themselves.

Here is the example of how these stream iterators are used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
istream_iterator<Sales_item> item_iter(cin), eof; 
ostream_iterator<Sales_item> out_iter(cout, "\n");

// Store the first transaction and read the next record
Sales_item sum = *item_iter++;
while (item_iter != eof) {
   // if the current transaction (stored in item_iter) has the same ISBN
   if (item_iter->isbn() == sum.isbn()) {
      sum += *item_iter++; // add to sum and read the next transaction
   } else {
      out_iter = sum; // Write the current sum to the assoc stream
      sum = *item_iter++; // Read the next transaction
   }
}


Here, the stream iterators are associated with cin and cout but I imagine they can be used with any type of streams (file, sstream, network).

I believe the istream_iterator and ostream_iterator invokes the <</>> operator that we must define in our custom class Foo to determine what must be done to read from or write to the associated stream.
I believe the istream_iterator and ostream_iterator invokes the <</>> operator that we must define in our custom class Foo to determine what must be done to read from or write to the associated stream.

Essentially you're right. Define the operator<< and operator>> for your class Foo as described in a tutorial of your choice, e.g.:
https://www.learncpp.com/cpp-tutorial/overloading-the-io-operators/

Typically, the string representation of the thing being streamed doesn't depend on the stream. For example, the textual representation of the integer forty-two is usually the same "42" no matter whether the stream is connected to a network, file, or standard I/O. That means dynamic_cast is usually unnecessary.

I've modified an example from learncpp, linked above, to use stream iterators:
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
#include <iostream>
#include <iterator>
#include <cmath>

class Point
{
public: // private:
    double m_x{};
    double m_y{};
    double m_z{};

public:
    Point(double x=0.0, double y=0.0, double z=0.0)
      : m_x{x}, m_y{y}, m_z{z}
    {
    }

    friend std::ostream& operator<< (std::ostream& out, const Point& point);
    friend std::istream& operator>> (std::istream& in, Point& point);
};

std::ostream& operator<< (std::ostream& out, const Point& point)
{
    // Since operator<< is a friend of the Point class, we can access Point's members directly.
    out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ')'; // actual output done here

    return out; // return std::ostream so we can chain calls to operator<<
}

std::istream& operator>> (std::istream& in, Point& point)
{
    // Since operator>> is a friend of the Point class, we can access Point's members directly.
    // note that parameter point must be non-const so we can modify the class members with the input values
    in >> point.m_x;
    in >> point.m_y;
    in >> point.m_z;

    return in;
}

Point negated(Point p) { return Point{-p.m_x, -p.m_y, -p.m_z}; }

int main()
{
  std::istream_iterator<Point> in  { std::cin }, sentinel; 
  std::ostream_iterator<Point> out { std::cout, "\n"}; 

  // or: std::transform(in, sentinel, out, negated);
  while (in != sentinel) *out++ = negated(*in++);  
}
https://coliru.stacked-crooked.com/a/32abc400eb76ef00
If you replace the std::cin and std::cout with streams of your choice (file stream, network stream, etc.) the code will still work.
Last edited on
@mbozzi I'm step-debugging through my own code example at the moment, which is essentially the same as the code example you'd provided, and have noticed a few things:

1. When istream_iterator<Point> in is created and bound to a stream (Line 45), a "dummy" Point object is default-initialized and the in iterator refers/points to this "dummy" object.

2. It looks like the program uses this "dummy" Point object as a "working" variable. When the stream is read from i.e., when you advance the iterator with operator++, the body of operator>> is invoked. Rather than creating a new Point object each time the iterator is advanced, the operator>> reuses this dummy variable.

As the class author, what behavior are you suppose to specify in the body of operator>>?

Apparently, reading from the stream inside operator>> is optional - you can choose to do nothing inside the body of operator>>! But the fact that you're given a reference to a stream and the same dummy variable seem to hint that the body of operator>> is where you should specify what to do with the data that is read in from the stream to initialize a Point object. In other words, you're implementing what it means to "read a Point object from the stream bound to the istream_iterator".

3. You'd written:
mbozzi wrote:

Typically, the string representation of the thing being streamed doesn't depend on the stream. For example, the textual representation of the integer forty-two is usually the same "42" no matter whether the stream is connected to a network, file, or standard I/O. That means dynamic_cast is usually unnecessary.


Is it possible for istream to contain multiple types e.g., int, double, string, or perhaps custom user-types?

I'm assuming that a stream can. If so, then presumably, the thing (could be a thread, or another application) writing to the stream and the thing reading from the stream has agreed on the format of the stream and what should happen when the other writes to or reads from the stream (e.g., "The stream shall have this format: a repeating sequence of a Foo object, a double, a string, and then the special sequence of 3 chars, '\0\0\0'"). This kind of specification would be necessary for the person implementing operator>>.

4. Can I specify specific input stream types instead of istream& operator>>(istream&, ...) to specify what should happen when Point is read from a file stream, vs. a string stream, vs. a network?
Last edited on
Is it possible for istream to contain multiple types e.g., int, double, string, or perhaps custom user-types?
istream is an alias for std::basic_istream<char, std::char_traits<char>>.
The first template argument means this is a stream of char (only).

An istream may contain the representations of any type in any order, but all of the the representations are transmitted through the stream as a sequence of char. For example the stream std::cin might contain the textual representation of a string followed by an int if you type "hello 42" at the keyboard.

As the class author, what behavior are you suppose to specify in the body of operator>>
From the sample code above:
1
2
3
4
5
6
7
8
std::istream& operator>> (std::istream& in, Point& point)
{
    in >> point.m_x;
    in >> point.m_y;
    in >> point.m_z;

    return in;
}
The operator>> defines how a Point should be deserialized from the contents of a stream. Equivalently, it defines how the sequence of char from the stream are translated into a Point object.

The opposite operator<< converts a Point into a sequence of char that represents it.

Can I specify specific input stream types instead of istream& operator>>(istream&, ...) to specify what should happen when Point is read from a file stream, vs. a string stream, vs. a network?

Yes, but it may be a little fragile.
Last edited on
Thanks, all. I feel I should create and post a thorough example before marking this question complete. Fun note, apparently, there's a book dedicated to streams: Standard C++ IOStreams and Locales by Langer, Kreft.
Last edited on
I have the book! My most recent C++-related purchase.
Standard C++ IOStreams and Locales is from 2000, C++ has updated how it deals with streams in C++11 and later.

I'm not saying the book is bad, but the approach is outdated by newer language features.

At least the C++ Primer book delves into C++11.
> Can I specify specific input stream types instead of istream& operator>>(istream&, ...)
> to specify what should happen when Point is read from a file stream, vs. a string stream, vs. a network?

No. The input stream type does not specify that it is dealing with a file etc.
For that, we need to look at the dynamic type of the associated stream buffer.

For example:
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
#include <iostream>
#include <fstream>
#include <sstream>

struct point { int x = 0 ; int y = 0 ; };

std::ostream& operator<< ( std::ostream& stm, point pt )
{
    const auto& write = [&]() -> std::ostream& { return stm << "point{" << pt.x << ',' << pt.y << '}' ; };

    const std::streambuf* streambuf = stm.rdbuf() ;
    if( streambuf == nullptr ) { /* there is no associated stream buffer */ }
    else if( dynamic_cast<const std::filebuf*>(streambuf) ) { stm << "to file: " ; write() ; /* write point to file */ }
    else if( dynamic_cast<const std::stringbuf*>(streambuf) ) { stm << "to string: " ; write() ; /* write point to string */ }
    // etc.
    else /* default */ write() ;

    return stm ;
}

int main()
{
    const point pt { 123, 456 };

    std::cout << pt << '\n' ; // write to stdout (default)

    std::filebuf fbuf ; fbuf.open( "output.txt", std::ios::out ) ;
    const auto oldbuf = std::cout.rdbuf( std::addressof(fbuf) ) ; // redirect output of std::cout to a file
    std::cout << pt << '\n' ; // write to the file
    std::cout.rdbuf(oldbuf) ; // restore std::cout's original buffer
}

Topic archived. No new replies allowed.