C++: Wrapping vector<char> with istream
Asked Answered
C

6

31

I want to wrap a vector<char> with std::istream (so reading the vector would be done through the istream interface)

What's the way to do it?

Chlordane answered 11/1, 2012 at 6:39 Comment(6)
Its not entirely clear what you want to be able to do. Can you give an example of the code that you would like to be able to write?Contentment
I use a library that expects an istream to consume. But I only have a vector at hand, hence I need to wrap it somehowChlordane
Does this suffice: #4992197Contentment
It's still not clear what you want to do. Do you want to read a std::vector<char> from a std::istream?Mawkish
Your other option if you dont want to copy is : https://mcmap.net/q/89568/-get-an-istream-from-a-char, and use &v[0] and &v[0]+v.size() as the arguments to membuf.Contentment
Related: stackoverflow.com/questions/1448467 stackoverflow.com/questions/1494182Schuh
C
38

You'd define a streambuf subclass wrapping the vector, and pass an instance of that to the istream constructor.

If the data does not change after construction, it is sufficient to set up the data pointers using streambuf::setg(); the default implementation for the other members does the right thing:

template<typename CharT, typename TraitsT = std::char_traits<CharT> >
class vectorwrapbuf : public std::basic_streambuf<CharT, TraitsT> {
public:
    vectorwrapbuf(std::vector<CharT> &vec) {
        setg(vec.data(), vec.data(), vec.data() + vec.size());
    }
};

std::vector<char> data;
// ...
vectorwrapbuf<char> databuf(data)
std::istream is(&databuf);

If you need anything more advanced than that, override the streambuf::underflow method.

Capriccio answered 11/1, 2012 at 6:54 Comment(9)
Thanks. can the ugly &*vec.begin() be replaced with vec.data() ?Chlordane
Sure, same as &*vec.end() can be replaced with vec.data() + vec.size().Capriccio
Note: std::istream doesn't free the pointer. Better create the streambuffer on stack.Foreground
Thanks, changed. The alternative would be to create an istream subclass that contains the streambuf -- note that in this case, the (protected) no-argument istream constructor and the init method need to be used as base classes are constructed before data members.Capriccio
@SimonRichter Could you please edit the second line and make it <CharT, TraitsT> instead of <CharT, Traits>? Just a minor typo. I would also make the constructor "explicit". Thanks.Colemancolemanite
p.s. check your parenthesis in the "setg(" line.Mechanist
With gcc 4.7 and later, you need to use this->setgTanyatanzania
With C++11 you might want to put in a move constructorPacorro
@PaulBruner, no, then I'd have to keep a copy of the vector inside the vectorwrapbuf.Capriccio
S
16

using Boost:

#include <boost/iostreams/stream.hpp>
#include <boost/iostreams/device/array.hpp>
using namespace boost::iostreams;

basic_array_source<char> input_source(&my_vector[0], my_vector.size());
stream<basic_array_source<char> > input_stream(input_source);

or even simpler:

#include <boost/interprocess/streams/bufferstream.hpp>
using namespace boost::interprocess;

bufferstream input_stream(&my_vector[0], my_vector.size());
Subjunctive answered 28/9, 2012 at 20:19 Comment(1)
I like the second one with a slightly modification for more C++'ish code: bufferstream input_stream(my_vector.date(), my_vector.size());Conjoined
C
5

Adapting the answer from Get an istream from a char* and assuming this is what you're trying to do:

// Forward declarations
std::vector<char> my_create_buffer();
void my_consume_buffer( std::istream & is );

// What you want to be able to write
std::vector<char> buffer = my_create_buffer();
my_consume_buffer( wrap_vector_as_istream(buffer) );

You can then create the wrap_vector_as_istream like this (untested though) :

#include <iostream>
#include <istream>
#include <streambuf>
#include <string>

struct wrap_vector_as_istream : std::streambuf
{
    wrap_vector_as_istream(std::vector<char> & vec ) {
        this->setg(&vec[0], &vec[0], &vec[0]+vec.size() );
    }
};

One thing to be aware of though. The object you've created contains pointers into the vectors memory. So if you add or remove values to the vector while having this wrapper floating around, then you're heading for a crash.

(Oh and if you up vote me, please up vote the post I've adapted this from.)

Contentment answered 11/1, 2012 at 7:5 Comment(0)
G
3

You'd could get away with simply building a class that implements the >> operator like a stream, something like this:

template<class _ITy>
class RangeStreamLite
{
private:

    _ITy Begin;
    _ITy End;
    _ITy Next;

public:

    RangeStreamLite(_ITy begin, _ITy end) :
        Begin(begin),
        End(end),
        Next(begin)
    {
        // Do nothing.
    }

    template<class _OTy>
    RangeStreamLite& operator>>(_OTy& out)
    {
        out = *Next;
        ++Next;
        return *this;
    }


    void reset()
    {
        Next = Begin;
    }
};

This is a 'quick and dirty' solution, a 'stream lite', it isn't really a stream in the proper sense but it works when all you require is a superficial stream-like device. To properly create a custom stream is a little more complicated, and would require you to inherit from std::streambuf and implement the necessary features. Here are a few links worth a look:

Gregoriagregorian answered 11/1, 2012 at 6:49 Comment(0)
C
0

If you are fine with swapping your vector<char> you can use Boost Interprocess' vectorstream. Example:

#include <boost/interprocess/streams/vectorstream.hpp>

#include <vector>
#include <string>
#include <iostream>


using namespace std;

int main(int argc, char **argv)
{
  static const char inp[] = "123 45 666"; 
  vector<char> v(inp, inp + sizeof inp  - 1);
  using ivectorstream =
    boost::interprocess::basic_ivectorstream<std::vector<char>>;
  ivectorstream is;
  is.swap_vector(v);
  while (!is.eof()) {
    int i = 0;
    is >> i;
    cout << i << '\n';
  }
  is.swap_vector(v);
  cout << string(v.begin(), v.end()) << '\n';
  return 0;
}

Alternatively, if you can't or don't want to mutate your vector<char>, you can use Boost Interprocess' bufferstream. With bufferstream, you don't have to swap your vector into it. Example:

#include <boost/interprocess/streams/bufferstream.hpp>

#include <vector>
#include <string>
#include <iostream>


using namespace std;

int main(int argc, char **argv)
{
  static const char inp[] = "123 45 666";
  vector<char> v(inp, inp + sizeof inp  - 1);
  boost::interprocess::ibufferstream is(v.data(), v.size());
  while (!is.eof()) {
    int i = 0;
    is >> i;
    cout << i << '\n';
  }
  return 0;
}
Clayborne answered 1/11, 2017 at 10:17 Comment(0)
K
-1

You will have to write a custom stream-implementation that inherits from istream. This can easily be done using Boost.Iostreams - all you'd have to do is implement a simple Source.

Kitchenware answered 11/1, 2012 at 6:56 Comment(1)
Downvote because indirection/linking to solutions instead of providing them is against the concept of Stack Overflow.Phosphorus

© 2022 - 2024 — McMap. All rights reserved.