r/wg21 · Posted by u/standard_committee_watcher · 8 hr. ago
P3655R3 - std::cstring_view WG21
42 comments · 93% upvoted

Paper: P3655R3 - std::cstring_view
Authors: Peter Bindels, Hana Dusíková, Jeremy Rifkin, Marco Foco, Alexey Shevlyakov
Target: LEWG, LWG
Ship vehicle: C++29

This paper proposes std::cstring_view, a non-owning view of a null-terminated string. Think string_view but with the guarantee that there's a '\0' at the end, so you can safely pass .c_str() to C APIs without first copying into a std::string.

The idea has been floating around since the original string_view paper in 2012. An earlier attempt (P1402) was rejected at Kona 2019 with "we will not pursue this problem space," but the paper argues that widespread adoption of custom implementations (2.1k+ on GitHub from Microsoft, Google, NVIDIA, and others) and the reflection paper P2996 essentially needing the type have changed the calculus. R3 incorporates NVIDIA's experience from their own zstring_view (formerly P3710) and feedback from SG16, SG23, and LEWG at Sofia.

Notable design decisions: the type eagerly computes size() at construction (no lazy evaluation), allows embedded NUL bytes, deletes remove_suffix() with a reason string, and provides a csv literal suffix. The naming was zstring_view through R2 but was changed to cstring_view in R3 to match c_str() naming.

▲ 289 points
sorted by: best
Stickied comment
u/r_cpp_janitor 8 hr. ago

Reminder: be civil. The paper authors sometimes read these threads.

u/UB_enjoyer_69 341 points 🏆 7 hr. ago

C++ and adding new string types. Name a more iconic duo.

u/not_a_real_cpp_dev 198 points 7 hr. ago

string, string_view, const char*, char[], cstring_view, u8string_view, u8cstring_view, wstring_view, wcstring_view...

And people wonder why beginners take one look at C++ and choose Go.

u/senior_nullptr 89 points 6 hr. ago

You forgot std::filesystem::path::string() which returns a std::string by value because of course it does.

u/daily_segfault 45 points 6 hr. ago

please stop

u/yet_another_cpp_dev 34 points 6 hr. ago

committee gonna committee

u/string_view_enjoyer 187 points 6 hr. ago

The framing most people will miss: this isn't "yet another string type." It's filling in the last cell of a 2x2 matrix that's existed since C++11:

OwningNon-owning
Null-terminatedstd::stringcstring_view
Not null-terminated(nobody needs this)string_view

The interesting design tension is in section 6.5. The paper chose eager size() calculation at construction. For a type whose primary purpose is C interop - where you're eventually calling c_str() and passing it to open() or sqlite3_open() or whatever - that means you're paying for a strlen you might not need. The paper argues this enables hardened preconditions and bounds-checking, which is the C++ answer, not the C answer.

The real value isn't in the API surface. c_str() and data() return the same pointer. The value is entirely in the type system: if your function takes a cstring_view, the compiler guarantees the caller provided something null-terminated. That's it. That's the whole paper. Everything else is implementation details.

The part that will generate the most heat: remove_suffix is = delete with a reason string. Which is correct - you can't chop the end off a null-terminated string without invalidating the guarantee. But I can already hear the complaints.

Edit: I should add - the csv literal suffix is going to be a problem. Every programmer on earth reads csv as "comma-separated values." They went with it to parallel sv for string_view, but the collision is real.

u/const_correct_or_die 67 points 5 hr. ago

The elephant in the room is section 6.4. The type allows embedded NUL bytes.

Let me reframe the question: what is cstring_view for? It exists so you can pass a string to C APIs that expect null-terminated input. A C function receiving csv.c_str() will see a string truncated at the first embedded NUL. The C side has no way to know there's more data after it.

If the type's entire purpose is to model "something safe to pass to C," then embedded NULs violate the invariant the type is supposed to represent. The precondition of strlen(csv.c_str()) == csv.size() is the actual contract with C. The paper chose not to enforce it.

u/legacy_api_survivor 54 points 4 hr. ago

We migrated ~300k lines from const char* to a custom zstring_view at work. If the type rejected embedded NULs, the conversion from std::string to cstring_view becomes partial - it works for 99.99% of strings and throws for the rest. That's worse than the current situation.

In practice, if you have a string with embedded NULs and you're passing it to a C API, the bug is in how you got the string, not in the view type. Making cstring_view reject it doesn't fix the bug - it just moves the error from the C API call to the conversion.

Also, LEWG already polled this in Sofia: 2/6/10/4/11 against forbidding them. The no-consensus was pretty decisive.

u/const_correct_or_die 38 points 3 hr. ago

The migration argument is exactly backwards. If you're converting std::string to cstring_view and it throws because of an embedded NUL, you've found a bug. That string was never safe to pass to C. The type is doing its job.

The "partial conversion" framing assumes all strings should be convertible to cstring_view. They shouldn't. Only null-terminated strings without embedded NULs are valid C strings. That's the domain the type models.

u/legacy_api_survivor 29 points 2 hr. ago

Philosophically you're right. Practically, adding an O(n) scan to every constructor for a problem that affects 0.01% of strings - and making stringcstring_viewstring_view lose data that stringstring_view preserves - is the kind of principled decision that makes library adoption fail.

I'd rather have the type exist and be useful than be theoretically perfect and avoided because the conversion is scary.

u/const_correct_or_die 15 points 47 min. ago

Fair. I'll take the type as-is and hope someone writes the follow-up paper for an embedded-NUL-free variant. The paper itself hints at this in section 6.4.

u/former_java_dev 12 points 5 hr. ago

Wait so this is basically Java's String but worse? At least Java strings just work without needing six different types to pass a piece of text around.

u/xX_template_wizard_Xx 45 points 5 hr. ago

Tell me you've never shipped production C++ without telling me you've never shipped production C++.

u/turbo_llama_9000 256 points 7 hr. ago

csv literal suffix? I got excited for a second thinking we finally got CSV parsing in the standard.

Then I remembered what committee I was dealing with.

u/constexpr_everything_2025 89 points 7 hr. ago

using namespace csv_literals;

coworker sees this in code review

"Oh cool, when did the standard add CSV support?"

"No it's for null-terminated string views"

"...why"

"Because sv was taken"

u/string_view_enjoyer 34 points 6 hr. ago

Yeah the csv naming is unfortunate. It parallels sv for string_view (c + sv = csv) which makes sense if you squint, but csv is one of the most overloaded abbreviations in computing. I'd bet money this comes up again in LEWG.

u/embedded_for_20_years STM32 apologist 89 points 5 hr. ago

Embedded developer here. Section 6.5 is where this paper loses me.

The whole pitch is: "your function takes a C string, use cstring_view instead of const char* to get bounds safety." Great. But the type eagerly computes size() via strlen in the const char* constructor. For us that's a non-trivial cost - we're passing paths to open() and config keys to C libraries, and we already know they're null-terminated string literals. We don't need the length. The C API doesn't need the length. Nobody needs the length.

The paper acknowledges this in 6.5 and says "users that really cannot afford that are still able to use const char*." Which is honest, but also means the type doesn't solve the problem for the people who have the problem the most.

I'll probably still use it where it matters, but the O(1)-from-literal constructor that skips the strlen would have been nice.

u/compiles_on_first_try 56 points 4 hr. ago

The eager size is what enables the hardened precondition on operator[] though. Without it you can't bounds-check. The paper is choosing safety over zero-cost, which is... actually the right call for a standard library type in 2025?

If you really need zero overhead, the (const char*, size_t) constructor is O(1) and just asserts the null terminator. For string literals, the length is a compile-time constant anyway.

u/embedded_for_20_years STM32 apologist 23 points 3 hr. ago

The (const char*, size_t) constructor is fine for literals if you use the csv suffix. For runtime C strings coming back from C libraries... you're back to const char*. Which is where we started.

Not a dealbreaker. Just a gap.

u/cargo_cult_cpp -12 points 7 hr. ago

Rust solved this years ago with CStr and CString. One is borrowed, one is owned. Clean, simple, no embedded NUL ambiguity because CStr rejects them. Meanwhile C++ is on its third attempt at a null-terminated string view and still can't decide on the name.

u/UB_enjoyer_69 67 points 6 hr. ago

Every. Single. Thread.

u/cargo_cult_cpp -3 points 6 hr. ago

I mean it's a valid comparison? Both languages have the exact same FFI problem. One solved it. One is writing papers about it.

u/r_cpp_janitor 6 hr. ago

Rule 3. Take a breath.

u/definitely_not_a_committee_member 134 points 6 hr. ago

Some context that might help people who weren't following in 2019:

P1402 proposed this exact type at Kona and was killed with "CONSENSUS: We will not pursue P1402R0 or this problem space." That was six years ago.

What changed:

• The Reflection paper (P2996) has APIs that return string_view but then specify that data()[size()] == '\0'. They literally needed the type and didn't have it.
• Herb's safety profiles paper (P3081) independently called out that a null-terminated view was one of the most commonly requested GSL features without a std:: equivalent.
• NVIDIA built their own (P3710), discovered the same design decisions independently, and merged into this paper.
• GitHub usage grew from ~1.9k to ~2.1k implementations. People are writing this type themselves and getting it wrong.

The committee killed it in 2019 because contracts were supposed to solve the problem. Contracts didn't ship. The problem is still there. This is the pragmatic fix.

The naming debate (cstring_view vs zstring_view) got polled in Sofia and hit no-consensus (8/6/13/4/2). The paper went with cstring_view anyway in R3, which is going to come up again.

u/the_real_stroustrup 23 points 5 hr. ago

So they rejected it in 2019 and now it's back? C++ standardization is just a zombie apocalypse with better catering.

[deleted] 7 hr. ago

[removed by moderator]

u/daily_segfault 8 points 6 hr. ago

what did they say?

u/not_a_real_cpp_dev 14 points 6 hr. ago

Something about how the committee is a waste of time and we should just fork clang, you know the usual.

u/legacy_api_survivor 97 points 5 hr. ago

For people who don't read papers, here's the concrete problem this solves:

// Today: bug-prone
void open_file(std::string_view path) {
    int fd = open(path.data(), O_RDONLY);
    // BUG if path came from substr()
}

// Today: works but allocates
void open_file(std::string_view path) {
    std::string owned(path);
    int fd = open(owned.c_str(), O_RDONLY);
}

// With cstring_view: safe and zero-copy
void open_file(std::cstring_view path) {
    int fd = open(path.c_str(), O_RDONLY);
    // guaranteed null-terminated
}

The first version is the bug the paper is trying to prevent. The second version works but allocates for no reason when the caller already had a null-terminated string. The third version is what you actually want.

I've found this exact bug pattern in production three times this year. The paper's GitHub examples aren't cherry-picked - this is everywhere.

u/xX_template_wizard_Xx 28 points 4 hr. ago

Skill issue. Just static_assert that your string_view is null-terminated. /s

u/coroutine_hater_2024 78 points 6 hr. ago

Can we please get networking into the standard before we add yet another string type? I've been waiting since C++11 and at this rate my grandchildren will be the ones writing std::net::tcp::socket.

u/constexpr_everything_2025 34 points 5 hr. ago

Networking will ship in C++38, right after we standardize a build system and achieve world peace.

u/professor_templates CS instructor 45 points 4 hr. ago

I teach intro C++ at a university. My students already struggle with when to use std::string vs const char* vs std::string_view. Now I need to add cstring_view to the decision tree.

That said - if it means I can stop explaining why string_view::data() isn't safe to pass to printf, I'll take the complexity tradeoff. The number of students who write printf("%s", sv.data()) where sv came from substr() is... non-zero.

u/not_a_real_cpp_dev 18 points 3 hr. ago

Honest question - when cstring_view ships, what's the actual decision tree? I genuinely don't know when to use what anymore.

u/string_view_enjoyer 42 points 2 hr. ago

It's simpler than it looks:

• Need to own the string? std::string
• Passing a string you don't own, don't need null termination? string_view
• Passing a string you don't own, need null termination (C API, fopen, etc.)? cstring_view
• C code calling you? const char*

That covers 99% of cases. The complexity is in the edge cases, not the common path.

u/build_system_victim_42 63 points 7 hr. ago

laughs in compile times

Great, another type that will take 10 years to get through LEWG and then 5 more years before any codebase can actually use it because they're stuck on C++17.

u/async_skeptic 112 points 5 hr. ago

Two things that jumped out:

1. The csv literal suffix is going to cause real confusion. Not just "haha funny acronym" confusion - I mean using namespace std::literals; will bring in csv as a suffix that has nothing to do with comma-separated values. IDE autocomplete, code search, onboarding - this will be a friction point for years.

2. The overload ambiguity (section 6.1) is more nuanced than the paper lets on. Yes, string vs string_view already has this problem with the godbolt example. But in practice most codebases don't overload on both. Adding cstring_view means any function currently taking string_view that wants null-termination now has to deal with three-way ambiguity. The requires true deprioritization trick works but it's not obvious and won't be discovered by most users.

The user should be clear about which properties of string it's expecting.

This is correct in theory and wishful thinking in practice. Most library authors will just pick one and hope for the best.

u/compiles_on_first_try 45 points 4 hr. ago

The requires true trick is galaxy-brain but ugly. I've used it in production and nobody on my team understood it until I explained it three times. If this is the official guidance for managing overload sets with cstring_view, it needs to be in a best-practices paper, not buried in section 6.1.1.

u/yet_another_cpp_dev 19 points 4 hr. ago

Can't wait for the csv literal to break every project that has a csv namespace for their actual CSV parsing code.

u/beman_contributor_42 37 points 4 hr. ago

For anyone who wants to try this today rather than wait for C++29: the Beman Project reference implementation is on GitHub. Header-only, works with C++20 and up.

It's also useful for seeing the design decisions in action. The remove_suffix = delete with the reason string is a nice touch - gives you an actual error message instead of a wall of template noise.