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.
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.