Constructing istringstream with string_view doesn't compile
Asked Answered
S

4

8

I'm not able to provide a std::string_view to std::istringstream's constructor. The following code doesn't compile (C++17 enabled using Clang v8):

std::string_view val = "Hello";
std::istringstream ss(val, std::ios_base::in);

The error I get is:

prog.cc:9:24: error: no matching constructor for initialization of 'std::istringstream' (aka 'basic_istringstream<char>')
    std::istringstream ss(val, std::ios_base::in);
                       ^  ~~~~~~~~~~~~~~~~~~~~~~
/opt/wandbox/clang-6.0.0/include/c++/v1/sstream:651:14: note: candidate constructor not viable: no known conversion from 'std::string_view' (aka 'basic_string_view<char>') to 'const std::__1::basic_istringstream<char, std::__1::char_traits<char>, std::__1::allocator<char> >::string_type' (aka 'const basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >') for 1st argument
    explicit basic_istringstream(const string_type& __s,
             ^

However this does:

std::string_view val = "Hello";
std::istringstream ss(val.data(), std::ios_base::in);

This issue is weird to me because there's only 1 implicit conversion that should be happening here: std::string_view to std::basic_string. The constructor is taking a basic_string according to the error message.

Why can't I use string_view verbatim here without calling string_view::data()?

Stipple answered 29/7, 2019 at 19:2 Comment(4)
string_view::data() is not even right, as the stringstream will be expecting a null-terminated string.Rhoden
I've been getting the feeling lately that string_view just makes things more complicated & confusing than before, where things were either std::string or char*. So far I'm not enjoying it.Stipple
btw, you've got UB in your "However this does" code snippet. The constructor will read off the end of the string_view, looking for a null char.Apus
An alternative input stream that can handle std::string_view can be found here: gist.github.com/andreasxp/ac9adcf8a2b37ac05ff7047f8728b3c7 (not my work!)Heretofore
A
8

This issue is weird to me because there's only 1 implicit conversion that should be happening here: std::string_view to std::basic_string.

A string_view is not implicitly convertible to a string. The constructor (ok, deduction guide, but whatever) is marked as explicit.

This should work (untested):

std::string_view val = "Hello";
std::istringstream ss(std::string(val), std::ios_base::in);

The reason for the explicit is that it's a (potentially) expensive operation; involving memory allocation and copying of the data. The opposite conversion (string --> string_view) is cheap, and as such is implicit.

Apus answered 29/7, 2019 at 19:12 Comment(7)
it's worth noting that while this "works", it pretty much defeats the purpose of using std::string_view in the first place. A real answer would involve writing a custom streambuf like so: https://mcmap.net/q/89612/-creating-an-input-stream-from-constant-memoryRhoden
I disagree - but not before C++20. In C++20, istringstream will have a constructor that takes a string && and moves it into the stream; thus saving an allocation/copy.Apus
string_view is used for much more than an alternative representation of a std::string. A simple example would be for extracting a substring out of a large text without having to make a copy of it.Rhoden
The point is that istringstream has to copy characters into itself. In C++17 (and before), you have to make a string (copy) and then copy them again into the stream. In C++20, the stream can just move-construct it's internal string from the string &&, saving an allocation and a copy.Apus
It's not about the copy/move of the std::string into the std::istringstream. It's the copy from the std::string_view into the std::string that cannot be avoided when using std::istringstream. std::string necessarily involves dynamic memory allocation, which may not be available - that is one of the main reasons to use std::string_view in the first place.Cusped
A std::istringstream has an internal std::string that stores the content of the stream. That's has to get filled somehow. If you don't want to use std::string, then std::istringstream is not the tool for you.Apus
Absolutely. The OP specifically asked for constructing std::istringstream from std::string_view, and your answer is valid and good. The point that user4442671 and I are trying to make is what you said: If you have a std::string_view, then likely, std::istringstream should not be on your shopping list in the first place. It doesn't matter if there are C++20 move constructors or not.Cusped
B
6

In addition to the other answers, if the intention to use stringview is to avoid copies of a char buffer, then copying it into std::string for istringstream is not satisfactory.

In that case, you can use Boost's iostream as a drop-in replacement for stringstream - this avoids copying the buffer.

#include <boost/iostreams/stream.hpp>

boost::iostreams::stream<boost::iostreams::array_source> stream(buffer, size);
Bags answered 11/11, 2021 at 22:27 Comment(0)
C
2

As of C++23: Use std::ispanstream

As pointed out by @Martshall Clow, there is no implicit conversion from std::string_view to std::istringstream, because the latter needs to manage an internal std::string, which in turn needs to own the memory containing the characters, but std::string_view doesn't own that memory. Thus, a copy is needed.

On the other hand, std::string_view was conceived explicitly with the purpose of not owning the characters, therefore, std::istringstream is fundamentally a bad match. Luckily, C++23 introduced <spanstream>, a streambuf implementation that works with fixed buffers not owning the memory. I presume it is named after the std::span and not a view, because output stream require mutable buffers, for which the std::span is the canonical tool. But for input streams, std::string_view and std::span<const char> are almost indistinguishable, and luckily, std::ispanstream includes a generic constructor overload taking a suitable read-only range which is able to take a std::string_view directly.

(Side node: Unfortunately, support is still somewhat limited. libC++ (Clang) doesn't support <spanstream> yet as of v18 - libstdc++ (GCC) on the other hand does so already since version v12.)

Cusped answered 5/7, 2024 at 12:0 Comment(0)
G
1

The issue here is that that std::string constructor that takes a std::string_view is marked explicit. This means you can't use it in an implicit conversion sequence.

You'll either need to add a cast to explicitly convert it, or use a std::string/const char[] instead.

Grummet answered 29/7, 2019 at 19:11 Comment(3)
If it's explicit, doesn't that defeat the purpose? That basically means you need to do std::string(val) everywhere. My thought on the matter was that implicit conversions would be key to elegant interoperability between std::string and std::string_view. Are they trying to make the more "wasteful" conversion be explicit as to avoid accidentally hurting performance?Stipple
@Stipple Exactly. Making a std::string could involve an allocation so it is potentially expensive and should be carefully considered. The opposite conversion though is cheap, and spitting out string_view's is OK.Grummet
Yes. (SO doesn't like short answers)Apus

© 2022 - 2025 — McMap. All rights reserved.