1

I have the following piece of code that used to work in OS X before I upgraded to OS X 10.9.4 This is the code:

#include <iostream>
#include <sstream>

int main() {
    // Buffer to be read
    const char* buffer = "0 3";
    std::cout << "buffer: '" << buffer << "'\n";
    size_t len = 3;

    std::istringstream iss;
    iss.clear();
    iss.rdbuf()->pubsetbuf((char*)buffer, len);

    // Did iss got a hold of buffer?
    std::cout << "str: '" << iss.str() << "'\n";
}

The idea is simple, and it has been already asked. I want to be able to read from an existing string and read from it without having to copy its contents. As I already mentioned, this code does not give the correct output in OS X 10.9.4.

My guess as to why this is the case, is because as it was mentioned in this post the implementation of pubsetbuf does nothing.

My question is: Is there a way of achieving the desired results in every machine? What would I need to do in order to set the internal buffer correctly?

In case this is not clear, I expect the following output:

buffer: '0 3'
str: '0 3'

The current output I'm obtaining in my machine is

buffer: '0 3'
str: ''

Meaning that the internal buffer in the istringstream was not set.

Community
  • 1
  • 1
jmlopez
  • 4,853
  • 4
  • 40
  • 74
  • There doesn't seem to be any portable way. In particular, the standard has this to say about `stringbuf::setbuf`: "Effects: implementation-defined, except that setbuf(0,0) has no effect." There's the (deprecated) [`std::istrstream`](http://en.cppreference.com/w/cpp/io/istrstream) class which specifically supports working off caller-provided buffer. – Igor Tandetnik Aug 13 '14 at 18:09
  • @IgorTandetnik This doesn't sound promising – jmlopez Aug 13 '14 at 18:26

2 Answers2

2

Instead of tampering with std::istringstream you might write your own:

#include <algorithm>
#include <ostream>

// BasicInputSequenceBuffer
// ============================================================================

template < typename Char, typename Traits = std::char_traits<Char> >
class BasicInputSequenceBuffer
:   public std::basic_streambuf<Char, Traits>
{
    // Types
    // =====

    private:
    typedef std::basic_streambuf<Char, Traits> Base;

    public:
    typedef typename Base::char_type char_type;
    typedef typename Base::int_type int_type;
    typedef typename Base::pos_type pos_type;
    typedef typename Base::off_type off_type;
    typedef typename Base::traits_type traits_type;
    typedef const char_type* pointer;
    typedef std::size_t size_type;

    // Construction
    // ============

    public:
    BasicInputSequenceBuffer(pointer data, size_type size) {
        // These casts are safe (no modification will take place):
        char* begin = const_cast<char_type*>(data);
        char* end = const_cast<char_type*>(data + size);
        this->setg(begin, begin, end);
    }

    // Stream Buffer Interface
    // =======================

    protected:
    virtual std::streamsize showmanyc();
    virtual std::streamsize xsgetn(char_type*, std::streamsize);
    virtual int_type pbackfail(int_type);

    // Utilities
    // =========

    protected:
    int_type eof() { return traits_type::eof(); }
    bool is_eof(int_type ch) { return ch == eof(); }
};


// Get Area
// ========

template <typename Char, typename Traits>
std::streamsize
BasicInputSequenceBuffer<Char, Traits>::showmanyc() {
    return this->egptr() - this->gptr();
}

template <typename Char, typename Traits>
std::streamsize
BasicInputSequenceBuffer<Char, Traits>::xsgetn(char_type* p, std::streamsize n) {
    std::streamsize result = std::min(n, this->egptr() - this->gptr());
    std::copy(this->gptr(), this->gptr() + result, p);
    this->gbump(result);
    return result;
}

template <typename Char, typename Traits>
typename BasicInputSequenceBuffer<Char, Traits>::int_type
BasicInputSequenceBuffer<Char, Traits>::pbackfail(int_type ch) {
    if(is_eof(ch)) {
        if(this->eback() != this->gptr()) {
            this->gbump(-1);
            return traits_type::to_int_type(*this->gptr());
        }
    }
    return eof();
}

typedef BasicInputSequenceBuffer<char> InputSequenceBuffer;


// BasicInputSequence
//=============================================================================

template < typename Char, typename Traits = std::char_traits<Char> >
class BasicInputSequence
:   public std::basic_istream<Char, Traits>
{
    // Types
    // =====

    private:
    typedef std::basic_istream<Char, Traits> Base;

    public:
    typedef typename Base::char_type char_type;
    typedef typename Base::int_type int_type;
    typedef typename Base::pos_type pos_type;
    typedef typename Base::off_type off_type;
    typedef typename Base::traits_type traits_type;

    private:
    typedef BasicInputSequenceBuffer<Char, Traits> buffer_type;

    public:
    typedef typename buffer_type::pointer pointer;
    typedef typename buffer_type::size_type size_type;

    // Construction
    // ============

    public:
    explicit BasicInputSequence(pointer data, size_type size)
    :   Base(&m_buf), m_buf(data, size)
    {}

    private:
    buffer_type m_buf;
};

typedef BasicInputSequence<char> InputSequence;

#include <iostream>

int main() {
    const char* buffer = "0 3";
    InputSequence stream(buffer, 3);
    std::string a;
    std::string b;
    stream >> a >> b;
    std::cout << "str: '" << a << ' ' << b << "'\n";
    return 0;
}
1

You can implement a stream buffer that takes a range of the characters you want to use as its buffer. Then wrap it with a convenience input stream:

#include <iostream>
#include <sstream>
#include <vector>

struct raw_char_buffer : std::streambuf
{
    explicit raw_char_buffer(const char* const begin,
                             const char* const end)
        : buffer(begin, end)
    {
        this->setg(buffer.data(), buffer.data(),
                   buffer.data() + buffer.size());
    }
private:
    std::vector<char> buffer;
};

struct raw_char_istream : virtual raw_char_buffer
                        , std::istream
{
    explicit raw_char_istream(const char* const begin,
                              const char* const end)
        : raw_char_buffer(begin, end)
        , std::istream(this)
    {
    }

    std::string str() const {
        return std::string(this->eback(), this->egptr());
    }
};

int main()
{
    const char* p = "Hello World";
    raw_char_istream os(p, p + 11);

    std::string s;
    os >> s;

    std::cout << "s = " << s << '\n';
    std::cout << "str() = " << os.str() << '\n';
}
David G
  • 94,763
  • 41
  • 167
  • 253
  • Hi David, thanks for your response. I like how we are using a vector to iterate through our data. There is one thing that worries me however, and that is the use of a virtual base class. Do you have any idea of how this would impact a program using `raw_char_istream` as compared to the regular `istringstream`? – jmlopez Aug 14 '14 at 03:27
  • @jmlopez There shouldn't be any difference. – David G Aug 14 '14 at 03:30
  • @jmlopez `virtual` is to protect against problems in using `raw_char_buffer` in other classes that may derive from the input stream class. Each class that inherits from `raw_char_istream` share the same base class subobject,`raw_char_buffer`. – David G Aug 14 '14 at 03:40
  • I was actually talking about performance, since the `istringstream` can be set to a different string by first making a copy this would waste both time and memory. On the other hand, if we just tell it where to read from then we would be doing things more efficiently. This seems to be the case here, but then again, I've been wrong many times before. Would you say `raw_char_istream` is more efficient? – jmlopez Aug 14 '14 at 03:44
  • You do not need expiicit in the constructor. It has two parameters. – LeDYoM May 19 '21 at 12:50