Why doesn't std::stringstream work with std::string_view?
Asked Answered
G

2

7

The std::stringstream initialization constructor accepts const string& as a parameter:

explicit stringstream (const string& str,
                       ios_base::openmode which = ios_base::in | ios_base::out);

This interface was reasonable in C++98, but since C++17 we have std::string_view as a cheaper alternative of the class representing a string. The std::stringstream class doesn't modify the string it accepts, doesn't own it, it doesn't require from it to be null-terminated. So why not to add another constructor overload that accepts the std::string_view? Are there any obstacles that make this solution impossible (or not reasonable) yielding to alternatives like Boost::Iostreams?

Garretson answered 3/9, 2022 at 4:19 Comment(2)
Likely because it just wasn't added. Nobody wrote the proposal, so it wasn't done. There many other places where this wasn't done: exception constructors, for example.Botanical
@StaceyGirl, that looks like an obvious answer, but it is strange to me that nothing is proposed to fix the issue even in C++23 (as far as I know).Garretson
F
10

At this point (ie: as we approach C++23), there's just not much point to it.

Since you used stringstream instead of one of the more usage-specific versions, there are two possibilities: you either intend to be able to write to the stream, or you don't.

If you don't intend to write to the stream, then you don't need the data to be copied. All forms of stringstream own the characters it acts on, so you should try to avoid the copy. You can use the C++23 type ispanstream (a replacement for the old strstream). This takes a span<const CharT>, but string_view should be compatible with one of ispanstream's constructors too.

If you do intend to write to the stream, then you will need to copy the data into the stringstream. But you need not perform two copies. So C++20 gives stringstream a move-constructor from a std::string. See constructor #6 here:

explicit basic_stringstream( std::basic_string<CharT,Traits,Allocator>&& str,
                             std::ios_base::openmode mode = 
                             std::ios_base::in | std::ios_base::out );
  1. Move-construct the contents of the underlying string device with str. The underlying basic_stringbuf object is constructed as basic_stringbuf<Char,Traits,Allocator>(std::move(str), mode).

And since std::string is constructable from a string_view, passing a std::string_view into a std::stringstream constructor will use this move-constructor overload, which should minimize copying.

So there's really no need for a string_view-specific constructor.

Fregoso answered 3/9, 2022 at 4:38 Comment(2)
Can you really std::move a std::string_view into a std::string ? You can copy-init or copy-assign a string_view to a string but this will make a copy of the string that backs the string view. (But maybe I'm misreading your comment.)Choanocyte
@MatthewHeaney: Yes, the std::string is copy constructed. But that string gets moved into the stringstream. Thus, it can "minimize copying". You need at least one copy because string_view is not modifiable.Fregoso
S
-1

How to efficiently (with std::move() if you like) construct a std::stringstream from a std::string, std::string_view, or C-string in C++11 or later

Quick summary

std::string str("Hello ");                            // std::string
constexpr char c_str[] = "Hey and how are you ";      // C-string
std::string_view sv(c_str);                           // std::string_view

std::stringstream ss1(str);                           // from a std::string
std::stringstream ss3(std::move(std::string(c_str))); // from a C-string
std::stringstream ss2(std::move(std::string(sv)));    // from a std::string_view

Details

I just updated the accepted answer by @Nicol Bolas with the C++20 move-constructor (constructor #6) definition and explanation from https://en.cppreference.com/w/cpp/io/basic_stringstream/basic_stringstream. If you're using C++11, however, you can still get this efficiency boost (as far as I can tell) simply by calling std::move() yourself.

With or without explicitly calling std::move(), all 3 techniques below work to construct a std::stringstream since C++11. So, having an extra constructor from std::string_view really isn't necessary, since you can already construct a std::stringstream from a std::string, from a std::string_view, or from a C-string (char array), as shown below.

You can play with some test examples like these in my stringstream_initialize_from_std_string__string_view__and_c_string.cpp file in my eRCaGuy_hello_world repo.

std::string str("Hello ");                          // std::string
constexpr char c_str[] = "Hey and how are you ";    // C-string
std::string_view sv(c_str);                         // std::string_view

// 1. Construct a `std::stringstream` from a `std::string`. This is
// constructor #3 from the link below: reference page for the
// `std::stringstream` constructors:
// https://en.cppreference.com/w/cpp/io/basic_stringstream/basic_stringstream
// - Open in mode `std::ios_base::app` as well in order to **append** all new
//   writes to the end of the stream! See link above **and**:
//   https://mcmap.net/q/717353/-how-to-append-content-to-stringstream-type-object
std::stringstream ss1(str, 
    std::ios_base::in | std::ios_base::out | std::ios_base::app);
ss1 << "world.\n";  // since `std::ios_base::app` was used above, this 
                    // **appends** rather than **overwrites** the data in 
                    // the stringstream.
std::cout << ss1.str() << "\n";

// 2. Construct a `std::stringstream` from a `std::string_view`. This is also
// constructor #3 from the link above (passing in a `std::string), but we must
// first construct a `std::string` from the `std::string_view`. We are using
// constructor #10 from the `std::string` constructors shown here to create a
// `std::string` from a `std::string_view`:
// https://en.cppreference.com/w/cpp/string/basic_string/basic_string
// - See also the note about the `std::ios_base::app` mode above.
std::stringstream ss2b(std::move(std::string(sv)),
    std::ios_base::in | std::ios_base::out | std::ios_base::app);
ss2b << "today?\n";
std::cout << ss2b.str() << "\n";

// 3. Construct a `std::stringstream` from a C-string. This is also
// constructor #3 from the link above (passing in a `std::string), but we must
// first construct a `std::string` from the C-string. We are using
// constructor #5 from the `std::string` constructors shown here to create a
// `std::string` from a C-string (`const char*`):
// https://en.cppreference.com/w/cpp/string/basic_string/basic_string
// - See also the note about the `std::ios_base::app` mode above. Note that the
//   C-string is used to automatically, implicitly construct a `std::string`
//   here, I believe.
//
// implicit construction of `std::string` from `c_str`
std::stringstream ss3c(std::move(c_str),
    std::ios_base::in | std::ios_base::out | std::ios_base::app);
ss3c << "doing?\n";
std::cout << ss3c.str();
// explicit construction of `std::string` from `c_str`
std::stringstream ss3d(std::move(std::string(c_str)),
    std::ios_base::in | std::ios_base::out | std::ios_base::app);
ss3d << "doing?\n";
std::cout << ss3d.str();

Sample output:

Hello world.
Hey and how are you today?
Hey and how are you doing?
Hey and how are you doing?

References

  1. std::stringstream constructors: https://en.cppreference.com/w/cpp/io/basic_stringstream/basic_stringstream
  2. constructor #10 from the std::string constructors shown here to create a std::string from a std::string_view: https://en.cppreference.com/w/cpp/string/basic_string/basic_string
    1. and constructor #5 from the std::string constructors shown here to create a std::string from a C-string (const char*)
  3. How to append content to stringstream type object?
Spue answered 3/9, 2022 at 6:26 Comment(4)
std::stringstream ss2(std::move(std::string(str))); // from a std::string_view Did you mean sv instead of str?Poker
Those two std::moves are unnecessary: std::stringstream ss3(std::move(std::string(c_str))); You don't need to do that for an rvalue. Only lvalues need to be moveed.Askwith
@digito_evo: What is your suggested change?Choanocyte
@MatthewHeaney I don't know if I remember correctly. I guess I was referring to the first two std::move(std::string(...)) which are redundant. It can do the same thing without calling std::move in that context.Askwith

© 2022 - 2024 — McMap. All rights reserved.