views:

258

answers:

1

Note: Edited based on responses to receive more appropriate answers.

I have a collection of C++ templates that I've made over the years, which I call Joop. It comprises mainly libraries that don't quite fall into the "general-purpose" category but are just useful enough that I keep slapping them into different projects, so most of them don't have equivalents in other libraries such as Boost.

One of these classes is seqstream. The idea is that it allows you to treat any iterable sequence as an ordinary STL-like stream, whose "character type" is the value type of the sequence.

The rationale for this class is twofold. First, it should present an interface that makes any potentially nonlinear, noncontiguous sequence look linear and contiguous; and second, it should treat any object in the stream as though it were a single, complex, large character. There is a standard means of treating a stream as a sequence, so why not the other way around?

At present, seqstream wraps three iterators for the first, last, and current element. I want to replace seqstream with a basic_seqbuf that can be plugged into a standard stream. Can anyone provide resources to get me started on extending std::basic_streambuf to provide this kind of behaviour?

Additionally, if a writable seqbuf is allowed, it is very that writing an object to the seqbuf does not serialise the object, but makes the appropriate call to an insert() method or uses a user-specified insert iterator, such as a std::back_insert_iterator.

Edit:

Here is an example of how seqstream is currently used:

// Create a sequence of objects.
std::vector<std::string> sequence;
for (int i = 0; i < 10; ++i) {
    std::ostringstream stream;
    stream << "Element " << i << ".";
    sequence.push_back(stream.str());
}

// Create a seqstream wrapping that sequence.
joop::seqstream< std::vector<std::string> > seqstream(sequence.begin(), sequence.end());

// Read the sequence like a stream.
std::string element;
while (seqstream >> element) // OR seqstream.get(element)
    std::cout << element << '\n';
+1  A: 

It can be confusing to look at the examples in sstream, but you probably don't want a new stream class at all. Looking now for an example at the basic_stringstream source, the only purpose of that class is to

  • provide str function (it just calls the underlying buffer's str)
  • avoid the underlying buffer's vtable when calling its methods
  • change rdbuf's return value to basic_stringbuf* (but that's unnecessary because an accessor for str was provided)

The stream classes do very little, and really aren't supposed to have any functionality besides calling an underlying buffer of type basic_streambuf. For example, I can do this:

string str( "Hello, world!" );
stringbuf buf( str ); // subclass of basic_streambuf
iostream pseudo_stringstream( &buf );
    // pseudo_stringstream can do anything a stringstream can do.
    // (not necessarily with the same syntax)

Moreover, all streams are supposed to inherit from either basic_istream, basic_ostream, or both. Inserter/extractor functions may not work if your stream doesn't inherit correctly. These inserter declarations are perfectly fine:

operator<<( ostream os, MyData d ); // not a template at all
       // templated, but requires correct inheritance:
template< class C > operator<<( basic_ostream<C> os, MyData d );

Therefore, if you want iostream behavior, you need to implement a subclass of basic_streambuf and attach it to a basic_iostream.


But, what is your actual goal? What is the advantage of a memory-backed stream over the usual iterators and maybe some back_insert_iterators? Do you want to use the same code for serialization as for iteration? You probably want to make the stream look like a sequence using stream_iterator, not to make the sequence look like a stream.

Potatoswatter
There are two goals. First, present an interface that makes any potentially nonlinear, noncontiguous sequence look linear and contiguous; and second, treat any object in the stream as though it were a single character. The stream idiom is a perfect match. `seqstream` replaces a `streambuf` with a sequence, and treats elements as characters. I'll look into whether making a subclass of `basic_streambuf` would work. I may need to split this into classes that separately fulfill the requirements.
Jon Purdy
@Jon: A sequence by definition already "looks linear." Contiguity means being able to do pointer arithmetic, and that works only if the container is a `vector`—streams don't help with that one bit. What does "treat… as a single character" mean? The point of streams is to provide buffered I/O, and you don't appear to be doing that at all.
Potatoswatter
@Potatoswatter: Granted on linear. Contiguity is the wrong term, too, but also the only term available: the stream is as contiguous as the sequence allows, quite simply because iterators are not pointers. "Treat as a single character" means don't break the object and don't serialise it. I am indeed doing buffered I/O, just with two generalisations: that the character can be any object, and that the buffer can be any sequence.
Jon Purdy
@Jon: It's not I/O if there's no serialization. The class isn't like a character unless you specialize `char_traits` for it.
Potatoswatter
@Potatoswatter: There doesn't need to be serialisation because the object type is the character type. You don't split a `char` when you write it to an `ofstream`. The problem is how to provide a sensible `int_type` for a generic `char_traits`. `char_type` should be the object type, so should `int_type` be the same? It must simply "represent any element of the controlled sequence as well as the value returned by `eof()`".
Jon Purdy
@Jon: This is your adventure ;v) . `boost::optional` can provide what you want: a single bit of additional state combined with optional initialization. Sequences don't need an EOF concept, just an end iterator. A stream iterator reaches a predefined value when it points to EOF. I'm still unconvinced that you are attempting more than a syntactic change from the existing STL.
Potatoswatter