# Tutorial

## 2.1.4. Writing a `container_device`

Suppose you want to write a Device for reading from and writing to an STL container. In order for combined reading and writing to be useful, you will also need to support seeking within the container. There are several types of Devices which combine reading and writing, whether there are two separate character sequences for input and output, or a single combined sequence, and on whether there are separate position indicators for reading and writing or a single read/write position indicator. See Modes for details.

A narrow-character Device for read-write access to a single character sequence with a single position indicator is called a SeekableDevice. A typical SeekableDevice looks like this:

    #include <iosfwd>                           // streamsize, seekdir
    #include <boost/iostreams/categories.hpp>   // seekable_device_tag
    #include <boost/iostreams/positioning.hpp>  // stream_offset

    namespace io = boost::iostreams;

    class my_device {
    public:
        typedef char                 char_type;
        typedef seekable_device_tag  category;

        std::streamsize read(char* s, std::streamsize n)
        {
            // Read up to n characters from the underlying data source
            // into the buffer s, returning the number of characters
            // read; return -1 to indicate EOF
        }

        std::streamsize write(const char* s, std::streamsize n)
        {
            // Write up to n characters to the underlying
            // data sink into the buffer s, returning the
            // number of characters written
        }

        stream_offset seek(stream_offset off, std::ios_base::seekdir way)
        {
            // Seek to position off and return the new stream
            // position. The argument way indicates how off is
            // interpretted:
            //    - std::ios_base::beg indicates an offset from the
            //      sequence beginning
            //    - std::ios_base::cur indicates an offset from the
            //      current character position
            //    - std::ios_base::end indicates an offset from the
            //      sequence end
        }

        /* Other members */
    };

Here the member type `char_type` indicates the type of characters handled by my_source, which will almost always be `char` or `wchar_t`. The member type category indicates which of the fundamental i/o operations are supported by the device. The category tag `seekable_tag` indicates that `read`, `write` and the single-sequence version of `seek` are supported.

The type `stream_offset` is used by the Iostreams library to hold stream offsets.

You could also write the above example as follows:

    #include <boost/iostreams/concepts.hpp>  // seekable_device

    class my_source : public seekable_device {
    public:
        std::streamsize read(char* s, std::streamsize n);
        std::streamsize write(const char* s, std::streamsize n);
        stream_offset seek(stream_offset off, std::ios_base::seekdir way);

        /* Other members */
    };

Here `seekable_device` is a convenience base class which provides the member types `char_type` and `category`, as well as no-op implementations of member functions `close` and `imbue`, not needed here.

You're now ready to write your `container_device`. Again, let's assume your container's iterators are RandomAccessIterators.

    #include <algorithm>                       // copy, min
    #include <iosfwd>                          // streamsize
    #include <boost/iostreams/categories.hpp>  // source_tag

    namespace boost { namespace iostreams { namespace example {

    template<typename Container>
    class container_device {
    public:
        typedef typename Container::value_type  char_type;
        typedef seekable_device_tag             category;
        container_device(Container& container)
            : container_(container), pos_(0)
            { }

        std::streamsize read(char_type* s, std::streamsize n)
        {
            using namespace std;
            streamsize amt = static_cast<streamsize>(container_.size() - pos_);
            streamsize result = (min)(n, amt);
            if (result != 0) {
                std::copy( container_.begin() + pos_,
                           container_.begin() + pos_ + result,
                           s );
                pos_ += result;
                return result;
            } else {
                return -1; // EOF
            }
        }
        std::streamsize write(const char_type* s, std::streamsize n)
        {
            using namespace std;
            streamsize result = 0;
            if (pos_ != container_.size()) {
                streamsize amt =
                    static_cast<streamsize>(container_.size() - pos_);
                streamsize result = (min)(n, amt);
                std::copy(s, s + result, container_.begin() + pos_);
                pos_ += result;
            }
            if (result < n) {
                container_.insert(container_.end(), s, s + n);
                pos_ = container_.size();
            }
            return n;
        }
        stream_offset seek(stream_offset off, std::ios_base::seekdir way)
        {
            using namespace std;

            // Determine new value of pos_
            stream_offset next;
            if (way == ios_bas::beg) {
                next = off;
            } else if (way == ios_bas::cur) {
                next = pos_ + off;
            } else if (way == ios_bas::end) {
                next = container_.size() + off - 1;
            }

            // Check for errors
            if (next < 0 || next >= container_.size())
                throw ios_base::failure("bad seek offset");

            pos_ = next;
            return pos_;
        }

        Container& container() { return container_; }
    private:
        typedef typename Container::size_type   size_type;
        Container&  container_;
        size_type   pos_;
    };

    } } } // End namespace boost::iostreams:example

Here, note that

- The member type `char_type` is defined to be equal to the containers's `value_type`;
- The member type `category` tells the Iostreams library that `container_device` is a model of SeekableDevice;
- A `container_device` can be constructed from a instance of `Container`, which is passed and stored by reference, and accessible via the member function `container()`;
- The implementation of `read` is identical to the implementation in `container_source`.

The implementation of `write` is a bit different from the implementation in `container_sink`. Rather than simply appending characters to the container, you first check whether the current character position is somewhere in the middle of the container. If it is, you attempt to satisfy the write request by overwriting existing characters in the conatiner, starting at the current character position. If you reach the end of the container before the write request is satisfied, you insert the remaining characters at the end.

The implementation of `seek` is striaghforward. First, you calculate the new character position based on `off` and `way`: if `way` is `ios_base::beg`, the new character position is simply `off`; if `way` is `ios_base::cur`, the new character position is `pos_ + off`; if `way` is `ios_base::end`, the new character position is `container_.size() + off - 1`. Next, you check whether the new character position is a valid offset, and throw an exception if it isn't. Instances of `std::basic_streambuf` are allowed to return `-1` to indicate failure, but the policy of the Boost Iostreams library is that errors should be indicated by throwing an exception (see Exceptions). Finally, you set the new position and return it.

You can use a container_device as follows

    #include <cassert>
    #include <ios> // ios_base::beg
    #include <string>
    #include <boost/iostreams/stream.hpp>
    #include <libs/iostreams/example/container_device.hpp>

    namespace io = boost::iostreams;
    namespace ex = boost::iostreams::example;

    int main()
    {
        using namespace std;
        typedef ex::container_device<string> string_device;

        string                            one, two;
        io::stream<string_device>  io(one);
        io << "Hello World!";
        io.flush();
        io.seekg(0, BOOST_IOS::beg); // seek to the beginning
        getline(io, two);
        assert(one == "Hello World!");
        assert(two == "Hello World!");
    }

Revised 20 May, 2004

© Copyright Jonathan Turkanis, 2004

Use, modification, and distribution are subject to the Boost Software License, Version 2.0. (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)