1

I'd like to wrap a const std::vector<char>& with a std::istream to forward to a library function. Various answers like this one recommend creating a class / struct which derives from std::streambuf and calls the setg method, but that method only takes in char_type*, not const char_type*. This makes sense, as std::streambuf is designed for both input and output, but that makes using it in a read-only capacity impossible without a couple of const_casts. In the <streambuf> header I don't see any read-only stream buffers (e.g. an istreambuf) which might enable a std::istream.

Is there another way to wrap const std::vector<char>& into a std::istream which doesn't require a copy, and doesn't use const_cast?

Vasu
  • 1,090
  • 3
  • 18
  • 35
  • On your side, `istream` makes it really hard to write to the underlying buffer by denying direct access to any write functions. If the API wants an `istream` you likely don't have to worry about protecting the `vector`. – user4581301 May 09 '23 at 21:58
  • Would `std::ostream_iterator` work for you? You can simply copy the vector to a stream. – Martin York May 09 '23 at 22:11
  • There are simpler techniques than writting an implementation of `streambuf`. You could use a read/write wrapper. `std::cout << MyVectorWriter(myVector) << "\n";` Here the class `MyVectorWriter` takes a reference to the vector which it stores internally and then defines the function: `std::ostream& operator<<(std::ostream& s, MyVectorWriter const& data)` to handle the writing of the vector to the stream. – Martin York May 09 '23 at 22:14
  • @MartinYork: the goal here seems to be to read from the vector as if it was a stream, not to write the vector to a stream. – Chris Dodd May 09 '23 at 23:25
  • The basic problem with using the (const) vector directly as the buffer, is that istreams support `unget`/`putback` which writes data back into the buffer. In order to support that, you'll need some sort of buffer copy that can hold the putback characters. – Chris Dodd May 09 '23 at 23:30

1 Answers1

3

A stream has a "get area" and a "put area", so even if you managed to write to your istream, the data wouldn't normally get written to the get area. You do have to pass the pointer to the buffer as char * instead of char const *, but it's reasonably safe to do so.

Here's a quick demo.

#include <istream>
#include <iostream>
#include <vector>
#include <streambuf>
#include <locale>

class vector_buf : public std::streambuf {
    std::vector<char> const& input;
    std::size_t current_pos = 0;
public:
    vector_buf(std::vector<char> const &input) : input(input) {
        setg((char *)input.data(), (char *)input.data(), (char *)&input.back());
    }

    int_type underflow() override {
        return std::char_traits<char>::eof();
    }
    int_type overflow(int_type) override {
        return std::char_traits<char>::eof();
    }
};

class vector_stream : public std::istream {
public:
    vector_stream(std::vector<char> const &input)
        : std::istream(new vector_buf(input))
    {}
};

int main() {
    char data[] = "abcdef";

    const std::vector<char> input(data, data + sizeof(data));

    vector_stream v {input};

    char ch;
    while (v >> ch)
        std::cout << "read: " << ch << '\n';
}

Although it's probably unnecessary, I've included an override of overflow, which would get called (and in this case fail) when/if somebody attempts a write.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Your vector here is not `const`, and if you make it `const`, the code won't compile. – Chris Dodd May 09 '23 at 23:22
  • @ChrisDodd: Sure it will (now). – Jerry Coffin May 09 '23 at 23:24
  • Yes, but you had to add an (explicit) cast removing the const, which will cause undefined behavior if anyone calls unget or putback on the stream. The OP is asking how to do it completely safely, without casting away the const. – Chris Dodd May 09 '23 at 23:32
  • @ChrisDodd: not so. If they've read at least one character, and they unget (or put back the same character they read), it will simply decrement the read pointer, so the next read will get that character again. Otherwise, it will call `pbackfail()`. You *can* override `pbackfail` to modify the data in the underlying storage--but I haven't done that, and by default it just fails. – Jerry Coffin May 09 '23 at 23:58