158

There is an implicit conversion from std::string to std::string_view and it's not considered unsafe, even though this surely may cause a lot of dangling references if the programmer is not careful.

On the other hand, there's no implicit conversion from std::string_view to std::string using same argument but in the completely opposite fashion: because the programmer may not be careful.

It's lovely that C++ has a replacement for a raw const char* pointer, while making it super confusing and stripped to the bone:

  • Implicit const char* -> std::string: OK
  • Implicit std::string_view -> std::string: NOPE
  • Assignment std::string = const char* : OK
  • Assignment std::string = std::string_view: OK
  • Appending std::string += const char* : OK
  • Appending std::string += std::string_view: OK
  • Concatenation const char* + std::string: OK
  • Concatenation std::string_view + std::string: NOPE
  • Concatenation std::string + const char*: OK
  • Concatenation std::string + std::string_view: NOPE

Am I missing something or is this a total nonsense?

In the end, how useful is this string view without all the crucial pieces that make it similar to const char*? What's the point of integrating it into the ecosystem of stdlib while not making the last step to make it complete? After all, if we need an object that represents a piece of a string we could write our own. Actually, a lot of libraries already have done that, years ago. The whole point of making something standard is to make it useful for widest range of use cases, isn't it?

Are they going to fix this in C++23?

8
  • 25
    I'm voting to close this question as off-topic because this isn't a question, this is a rant. Commented Nov 28, 2017 at 12:57
  • 4
    This probably belongs on reddit.com/r/cpp (some C++ committee members read that.) Commented Nov 30, 2017 at 8:29
  • 65
    It seems some unfair that this question, whose discussion would help many programmers, has been closed Commented Jun 17, 2018 at 15:27
  • 43
    @Barry, this is indeed a question. It's also a rant, but that doesn't make it not a question. The OP is expressing legitimate shock, quite understandable IMO, which doesn't at all detract from the technical legitimacy and usefulness of the question. It would be great if the question could be reopened. Commented Jul 18, 2019 at 19:11
  • 9
    "Seriously, whats't up." Sounds like a question to me. ;) Commented Dec 20, 2019 at 22:33

3 Answers 3

68

The problem is that std::string_view -> std::string makes a copy of the underlying memory, complete with heap allocation, whereas the implicit std::string -> std::string_view does not. If you've bothered to use a std::string_view in the first place then you obviously care about copies, so you don't want one to happen implicitly.

Consider this example:

void foo1(const std::string& x)
{
    foo2(x);
}
void foo2(std::string_view x)
{
    foo3(x);
}
void foo3(const std::string& x)
{
    // Use x...
}

The function foo2 could've used a const std::string& parameter, but used a std::string_view so that it is more efficient if you pass in a string that isn't a std::string; no surprises there. But it's less efficient than if you'd just given it a const std::string& parameter!

  • When foo2 is called with a std::string argument (e.g. by foo1): When foo2 calls foo3, it creates a copy of the string. If it had a const std::string& argument, it could've used the object it already had.
  • When foo2 is called with a const char* argument: A std::string copy has to be made sooner or later; with a const std::string& parameter it gets made earlier, but overall there's exactly one copy either way.

Now imagine foo2 calls multiple functions like foo3, or calls foo3 in a loop; it's making exactly the same std::string object over and over. You'd want the compiler to notify you about this.

Sign up to request clarification or add additional context in comments.

22 Comments

From my experience, most often std::string_view is used to optimize parsing of a string. The result of parsing, obviously, cannot be stored in a view, so it should be transferred to a permanent storage (std::string). Why should it be so verbose? After all, I am in control and i should know where view may be converted.
@GreenScape I don't understand the point in your first comment; maybe you could add some example code to your question? I also don't really understand your second point; what exactly is bad design? The fact that foo3 takes a const std::string&? That was extremely common before std::string_view existed, and there is a lot of that code lying around. Or the implementation of foo2? Because I agree (although it's bad implementation not bad design) but it can easily happen by accident, and you would want the compiler to catch it.
@ArthurTacca what would you say if string_view was replaced by const char* in your example ? it's been decades we deal with const char* -> string implicit conversion. string_view helps to manipulate temp const char* memory, it is obvious that at a moment the string_view will trigger allocation by converting into a string, and it is the responsability of the user to ensure where. More, an old code built with const std::string& everywhere couldn't translate easily with string_view because of that missing implicit conversion.
@Juicebox That is a totally different scenario. For a start, string literals are const char* so there needs to be an implicit conversion so that f("foo") works when f takes a std::string (there would be riots in the street if it didn't). Second, you only use string_view if you are specifically trying to avoid memory allocation - that's the whole point of my answer - but that is certainly not the whole point of const char*.
You say passing std::string_view is less efficient than using const std::string &. This test dramatically shows the opposite. What conditions would make your statement true?
|
27

Because expensive implicit conversions are undesirable...

You only listed one implicit conversion out of your entire set of examples: const char* -> std::string; and you're asking for another one. In all of the other 'OK'-marked functions - allocations of memory are obvious/explicit:

  • Assignments: When you assign anything to an owning object with variable storage size, it is understood that an allocation may be necessary. (Unless it's a move-assignment, but never mind that.)
  • Append I: when you append to an owning object with variable storage size, it is even more obvious it will need to allocate to accommodate the additional data. (It might have enough reserved space, but nobody guarantees this about strings.)
  • Concatenations: In all three cases, memory is allocated for the result only, not for any intermediate object. Allocation for the result is obviously necessary, since the operands of the concatenation remain intact (and can't be assumed to hold the result anyway).

Implicit conversions in general have benefits and detriments. C++ is actually mostly stingy with these, and inherits most implicit conversions from C. But - const char* to std::string is an exception. As @ArthurTacca notes, it allocates memory. Now, the C++ core guidelines say:

C.164: Avoid implicit conversion operators

Reason:
Implicit conversions can be essential (e.g., double to int) but often cause surprises (e.g., String to C-style string).

and this is doubly the case when the unintended conversion performs expensive operations like allocation, which have side effects, and may make system calls.


PS - std::string_view has quite a few gotchas; see this CppCon 2018 talk by Victor Ciura:

Enough string_view to hang ourselves with

So remember it's not some kind of universal panacea; it's another class that you need to use with care, not carelessness.

... an explicit constructor is sufficient.

There actually does exist an explicit constructor of std::string from std::string_view. Use it - by passing std::string{my_string_view} to a function taking a string.

10 Comments

Why? Is it such a disaster? What is the worst-case scenario? An extra allocation? It can be easily profiled and optimized, if necessary. On the other hand, we have a handicapped API. Handicapped API is not that easy to work-around. Such an API is disgusting to use: tons of boilerplate just to avoid this 'mysterious catastrophic implicit conversion'. And remember, we're talking about a non-ordinary type. That has much more severe gotcha: dangling references. And we're fine with that. But not with an implicit allocation, oh no!
"Which means that you're going to use explicit std::string{foo} everywhere" - if this is the case, then you probably shouldn't be using string_view in the first place. Just use strings then. Most of the point of using string_view's in the first place is to avoid these allocations.
@GreenScape, I totally agree with you. I decided to replace "std::string const &" with "string_view" for all possible function arguments in my libs, and that ended up costing me literally days of work boilerplating my code to fix all the sudden implicit conversion errors (which were all desirable conversion in the context of my program). It got me thinking that the comittee is so wrong on this matter, because is not their role to patronize the way I code.
There are many problems with this inconsistent design decision. I brought it up with members of the standard committee before C++17 was finalized, but their desire to prevent silent allocations trumped all. I told them adding an annoying inconsistency into the standard in an attempt to prematurely optimize user code would not even work, as people would work around it, that the inconsistency could result in other sub-optimal code, and that static analysis would be a much better solution. Alas, my concerns were dismissed and so here we are.
@JosephThomson: Well, I've been bitter for a while about the rejection of uniform call syntax proposals for what I would call "parochial" reasons, so... I guess I can sympathize.
|
5

One of the reasons string views are useful in embedded environment is precisely because they don't do dynamic allocation and we get safety due to the length being passed as part of the view. So for me the lack of an implicit cast to std::string is not a deal breaker because that would require dynamic allocation. A silent conversion would be an error in this environment and have to be found and removed.

3 Comments

Excuse me, but how much C++ code is embedded code? To be truthful, your domain ditches half of C++ features anyway (forbids their use). So, given how much code you have to write anyway, to replace ditched features of stdlib, you can easily write your own string_view. My vision is: stdlib must target the most common and widespread use-cases. And make the use of it features as comfortable as possible. All other corner cases can be written for specific domain. Ie, that's why stdlib doesn't have BTree or Trie implementation.
@GreenScape not saying I like working with embedded C++. But a lot of embedded code is written in c++, mbed-os, pigweed, arduino, etl. string_view was a welcome addition for developing in embedded c++. I don't think the standard was written with embedded development in mind, but string_view just so happens to work well for embedded development.
The is a surprisingly large amount of embedded C++ code out in wild because it actually often compiles into more efficient code then C. Also there are many features of C++ that are really useful such as templates, constexpr, function overloading, much better type safety, RAII etc.std::string_view increases safety for no additional effort on the developers part, so its really useful in embedded.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.