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.
Reminder: be civil. The paper authors sometimes read these threads.
C++ and adding new string types. Name a more iconic duo.
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.
You forgot
std::filesystem::path::string()which returns astd::stringby value because of course it does.please stop
committee gonna committee
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:
std::stringcstring_viewstring_viewThe 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 callingc_str()and passing it toopen()orsqlite3_open()or whatever - that means you're paying for astrlenyou 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()anddata()return the same pointer. The value is entirely in the type system: if your function takes acstring_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_suffixis= deletewith 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
csvliteral suffix is going to be a problem. Every programmer on earth readscsvas "comma-separated values." They went with it to parallelsvforstring_view, but the collision is real.The elephant in the room is section 6.4. The type allows embedded NUL bytes.
Let me reframe the question: what is
cstring_viewfor? It exists so you can pass a string to C APIs that expect null-terminated input. A C function receivingcsv.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.We migrated ~300k lines from
const char*to a customzstring_viewat work. If the type rejected embedded NULs, the conversion fromstd::stringtocstring_viewbecomes 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_viewreject 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.
The migration argument is exactly backwards. If you're converting
std::stringtocstring_viewand 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.Philosophically you're right. Practically, adding an O(n) scan to every constructor for a problem that affects 0.01% of strings - and making
string→cstring_view→string_viewlose data thatstring→string_viewpreserves - 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.
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.
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.
Tell me you've never shipped production C++ without telling me you've never shipped production C++.
csvliteral 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.
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
svwas taken"Yeah the
csvnaming is unfortunate. It parallelssvforstring_view(c+sv=csv) which makes sense if you squint, butcsvis one of the most overloaded abbreviations in computing. I'd bet money this comes up again in LEWG.Embedded developer here. Section 6.5 is where this paper loses me.
The whole pitch is: "your function takes a C string, use
cstring_viewinstead ofconst char*to get bounds safety." Great. But the type eagerly computessize()viastrlenin theconst char*constructor. For us that's a non-trivial cost - we're passing paths toopen()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
strlenwould have been nice.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.The
(const char*, size_t)constructor is fine for literals if you use thecsvsuffix. For runtime C strings coming back from C libraries... you're back toconst char*. Which is where we started.Not a dealbreaker. Just a gap.
Rust solved this years ago with
CStrandCString. One is borrowed, one is owned. Clean, simple, no embedded NUL ambiguity becauseCStrrejects them. Meanwhile C++ is on its third attempt at a null-terminated string view and still can't decide on the name.Every. Single. Thread.
I mean it's a valid comparison? Both languages have the exact same FFI problem. One solved it. One is writing papers about it.
Rule 3. Take a breath.
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_viewbut then specify thatdata()[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_viewvszstring_view) got polled in Sofia and hit no-consensus (8/6/13/4/2). The paper went withcstring_viewanyway in R3, which is going to come up again.So they rejected it in 2019 and now it's back? C++ standardization is just a zombie apocalypse with better catering.
[removed by moderator]
what did they say?
Something about how the committee is a waste of time and we should just fork clang, you know the usual.
For people who don't read papers, here's the concrete problem this solves:
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.
Skill issue. Just
static_assertthat yourstring_viewis null-terminated. /sCan 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.Networking will ship in C++38, right after we standardize a build system and achieve world peace.
I teach intro C++ at a university. My students already struggle with when to use
std::stringvsconst char*vsstd::string_view. Now I need to addcstring_viewto the decision tree.That said - if it means I can stop explaining why
string_view::data()isn't safe to pass toprintf, I'll take the complexity tradeoff. The number of students who writeprintf("%s", sv.data())wheresvcame fromsubstr()is... non-zero.Honest question - when
cstring_viewships, what's the actual decision tree? I genuinely don't know when to use what anymore.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.
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.
Two things that jumped out:
1. The
csvliteral suffix is going to cause real confusion. Not just "haha funny acronym" confusion - I meanusing namespace std::literals;will bring incsvas 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,
stringvsstring_viewalready has this problem with the godbolt example. But in practice most codebases don't overload on both. Addingcstring_viewmeans any function currently takingstring_viewthat wants null-termination now has to deal with three-way ambiguity. Therequires truedeprioritization trick works but it's not obvious and won't be discovered by most users.This is correct in theory and wishful thinking in practice. Most library authors will just pick one and hope for the best.
The
requires truetrick 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 withcstring_view, it needs to be in a best-practices paper, not buried in section 6.1.1.Can't wait for the
csvliteral to break every project that has acsvnamespace for their actual CSV parsing code.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 = deletewith the reason string is a nice touch - gives you an actual error message instead of a wall of template noise.The C++ job market is changing fast. Learn modern C++ in 30 days with our bootcamp. 10x your career. Link in bio.