c++stringstream

Can I get the raw pointer for a std::stringstream accumulated data with 0-copy?


Here is what I would like to do:

std::stringstream s;
s<<"Some "<<std::hex<<123<<" hex data"<<...;

Having this s, I would very much like to pass it around, and that is possible easily. However, at some point, there is a need to (conceptually) pass it to an interface that only accepts const void */size_t pair which describes the memory region.

As far as I see (would love to stand corrected), there is no way to do that with 0-copy in C++17 and below. One must use .str() which will create a string copy and then take it from there.

As a "hack", this is what I came up with:

struct stringstream_extractor_t {
    // this structure will be used to get access to pbase member function which is protected
    // the way we do that is to inherit from it
    template <typename T>
    struct accessor_t : public T {
        static const char* data(const accessor_t* pT) { return pT->pbase(); }
    };

    // get the return type for std::stringstream::rdbuf
    using bufferType = std::remove_pointer<decltype(((std::stringstream*)nullptr)->rdbuf())>::type;

    // having the std::stringstream::rdbuf result type, we can now create our accessor type
    // which will be able to call pbase inside it
    using accessorType = accessor_t<bufferType>;

    // this is where we deposit the data, in a const manner
    const std::string_view _data;

    // syntactic sugar: init the _data with the stuff from stream reference
    stringstream_extractor_t(std::stringstream& stream) : _data{getBuffer(stream), static_cast<size_t>(stream.tellp())} {}

    // syntactic sugar: init the _data with the stuff from stream pointer
    stringstream_extractor_t(std::stringstream* pStream) :
        _data{pStream ? getBuffer(*pStream) : nullptr, pStream ? static_cast<size_t>(pStream->tellp()) : 0} {}

    // this uses the accessor type to grab access to data
    static const char* getBuffer(const std::stringstream& stream) {
        // we get the buffer and we cast it to our accessor type. This is safe, as we do not
        // have any members inside it, just a static function
        const accessorType* pBuffer = reinterpret_cast<accessorType*>(stream.rdbuf());

        // grab the data now
        return accessorType::data(pBuffer);
    }

    // convenience functionality
    inline const char* data() const { return _data.data(); }
    inline size_t size() const { return _data.size(); }
};

And here is how it is used, with a C-like interface

std::stringstream s;
s << "Hi there! " << std::hex << 0xdeadbeef;

const stringstream_extractor_t e(s);
write(2, e.data(), e.size());

Yes, I'm aware of the fact that the pointers must be kept alive (the std::stringstream instance), and all the life cycle implications.

Is there a more comfortable non-convoluted way to achieve this quite basic thing: get a buffer out from an output stringstream with move semantics.

I'm clearly missing something, this should not be this hard.

https://godbolt.org/z/G53P6oocb


Solution

  • Well in C++ 20 you could do this

    #include <iostream>
    #include <ios>
    #include <sstream>
    
    
    void c_style_func(const char* cp, std::size_t size) {
        std::cout << std::string_view  (cp,size) << "\n";
    }
    
    int main() {
        std::stringstream s;
        s << "Hi there! " << std::hex << 0xdeadbeef;
        auto view = s.view();
        c_style_func(view.data(), view.size());
    }
    

    using § 29.8.2.4 Page of the standard 1411

    basic_string_view<charT, traits> view() const noexcept; 11 Let sv be basic_string_view<charT, traits>. 12 Returns: A sv object referring to the basic_stringbuf’s underlying character sequence in buf: (12.1) — If ios_base::out is set in mode, then sv(pbase(), high_mark-pbase()) is returned. (12.2) — Otherwise, if ios_base::in is set in mode, then sv(eback(), egptr()-eback()) is returned. (12.3) — Otherwise, sv() is returned. 13 [Note: Using the returned sv object after destruction or invalidation of the character sequence underlying *this is undefined behavior, unless sv.empty() is true. —end note]


    Supplemental 2024:

    std::string_view.data is, in contrast to std::string::data, not guaranteed and usually is not null terminated. So the usage above is still safe, as it does not assume a NTS, but be aware if you use any function only taking a char* in general.