std::cstring_view

Document # P3655R6
Date 2026-10-18
Targeted subgroups LEWG, LWG
Ship vehicle C++29
Reply-to Peter Bindels <dascandy@gmail.com>
Hana Dusíková <hanicka@hanicka.net>
Jeremy Rifkin <jeremy@rifkin.dev>
Marco Foco <marco.foco@gmail.com>
Alexey Shevlyakov <aleshevl@gmail.com>
Thomas Köppe <tkoeppe@google.com>
Xavier Bonaventura <xavier.bonaventura@bmw.de>

1 Abstract

We propose a standard string view type that guarantees null-termination.

2 Introduction

C++17 introduced std::string_view, a non-owning view of a continuous sequence of characters. It is cheap to use, offers fast operations, and replaced most uses of const std::string& or const char* as function parameters. The utility and interface of string views as well as the benefits of not having to do unnecessary strlen calculations on C-style strings makes string view types highly desirable for working with strings in C++.

Unlike const std::string& and many but not all const char*, std::string_view is not null-terminated. This allows it to have fast and cheap substring operations. However, this also means std::string_view is not a suitable replacement for either of the two aforementioned types whenever a null-terminated C-style string is needed. While most C++ code mostly interfaces with C++ code, it is not uncommon to need to use operating system calls, C interfaces, third-party library APIs, or even C++ standard library APIs which require null-terminated strings. Because of a lack of a desirable option for passing non-owned null-terminated strings, std::string_view parameters are nonetheless sometimes used today in cases where null-terminated strings are needed, calling std::string_view::data to get a const char*. This is, needless to say, very bug-prone.

For this reason, many C++ developers use custom zstring_view or cstring_view types which are guaranteed to be null-terminated. Its wide presence on Github with implementations from among others Microsoft, Google, and many smaller projects, indicates a wide support for the type. As it's a lingua franca type, it should be part of the standard C++ library.

3 Revision History

3.1 R0, February 2025

Initial draft

3.2 R1, May 2025

3.3 R2, June 2025

3.4 R3, October 2025

3.5 R4, March 2026

3.6 R5, June 2026

3.7 R6, October 2026

4 Previous Papers

The idea of cstring_view was present even in the original paper for string_view (Sept 2012 - Feb 2014) N3921:

Another option would be to define a separate zstring_view class to represent null-terminated strings and let it decay to string_view when necessary. That's plausible but not part of this proposal.

An attempt was made in Feb 2019 to concretely propose the type (renamed to cstring_view) with P1402, but it failed to gain consensus in LEWGI at Kona. In fact, the discussion there concluded with "CONSENSUS: We will not pursue P1402R0 or this problem space." It is also worth noting that contracts were briefly contemplated in the minutes as a way of addressing the problem, however, contracts can't check for a null character safely as it wouldn't be part of the view. Similarly, other runtime checks for string_view null-termination are off the table.

However, in 2024 P3081R1 Core safety profiles for C++26 was published which contains an aside regarding a null-terminated cstring_view in section 8, noting:

This is one of the commonly-requested features from the GSL library that does not yet have a std:: equivalent. It was specifically requested by several reviewers of this work.

During the discussion, it was made clear that cstring_view deserves attention on its own and it should not be happenstance introduced as part of an entirely different topic. While P3081 did not end up passing for reasons other than cstring_view, it is another example of the utility being seen as desirable.

We also have P2996 Reflection for C++26, which in its specification of functions returning a string_view or u8string_view tries its best to say it's actually supposed to be a cstring_view, except without naming the type for existential reasons:

Any function in namespace std::meta that whose return type is string_view or u8string_view returns an object V such that V.data()[V.size()] == '\0'.

As such, we do believe that there is both space for such a type, and a desire from multiple angles to have it defined in the standard library.

5 Discussion on the type

5.1 Reasons to have the type

Many functions right now whether C++ standard library calls, operating system calls, or third-party library calls require null-terminated C-style strings. Absent an efficient type that can represent a non-owning view of a null-terminated string, these functions must take either a const char* and incur potential strlen overhead, take a const std::string& and possibly superfluously copy and allocate, take a string_view and make a copy, or haphazardly take a std::string_view with a fragile unenforceable precondition that it is a view of a null-terminated string. Such a precondition would be unenforceable because pre(sv[sv.size()] == 0) is undefined behavior.

String views tend to start their life coming from a string literal or from a type that owns its internal buffer, often std::string. Both of these are places that can always create a cstring_view instead, as they both know their data is inherently null terminated.

Strings are very often just passed along with a non-owning string type all the way to where they are used as data. Some of these potential uses, such as calling std::ofstream::write, do not rely on the null terminator, but others, like std::ofstream::ofstream, do. Using string_view as the intermediate type loses the knowledge that a null terminator is already guaranteed to be present, requiring the developer to either make assumptions or make a copy. Another common use might be creating a stringstream from a string StackOverflow question.

We do not have to look far for examples of std::string_view being used in bug-prone ways with APIs expecting null-terminators. Searching /\.data\(\)/ string_view language:c++ -is:fork on GitHub code search turns up two examples on the first page:

From surge-synthesizer/shortcircuit-xt

std::optional<std::string> hash_file(const std::string_view &file_name) { auto fd = open(file_name.data(), O_RDONLY); ...

From VisualGMQ/gmq_header

void log(Level level, std::string_view funcName, std::string_view filename, unsigned int line, Args&&... args) { if (level <= level_) { printf("[%s][%s][%s][%u]", Level2Str(level).data(), filename.data(), funcName.data(), line); ...

These two examples could be argued to be bad code or misuses of std::string_view, however, the fact that they are written reflects the desire for the ability to use string view types in such cases.

These problems are visible enough that we have targeted patches for specific instances of this problem in the standard - P2495 attempts to directly patch this specific example. The solution bypasses that the problem is that we're missing an unbroken type-safe chain of knowledge that the value being passed is or is not null-terminated.

The type effectively fills out the design space that exists around strings within C++. We started off with std::string as an owning string type, ambiguously being specified as having a null terminator or not, clarified in C++11 to definitely *have* a null terminator. C++14 added std::string_view to that set, offering a non-null-terminated non-owning string type. The type itself raises the question, should we add a non-null-terminated owning string type, and/or a null-terminated non-owning string type? We're proposing the last of these, leaving only the non-null-terminated owning string type as not present. We believe there is no situation to be written where a non-null-terminated owning string type would have a measurable benefit over the null-terminated owning string type that we have, and as such it is not worth the added complexity.

Without them we retain the question we had before - const char*, const std::string& or string_view? The first loses lots of type safety and potentially requires redundant strlen computation elsewhere in the software, the second requires it to be a std::string but retains null-termination knowledge, the last offers the ability to send any string type constructible to a string_view, but loses any knowledge of null termination. For the common case of passing along "some string input" to a function that ends up requiring null termination, the proposed cstring_view is the only type that properly captures it.

Since the first draft of this paper the use of cstring_view and zstring_view on Github has grown from 1.9k to 2.1k, indicating active use, and in many cases people adding new implementations of the type. Many contain the subtle bugs that we highlight in this paper, illustrating why it is a good idea to add the type to the standard.

5.2 Reasons not to have the type

In an ideal world, we could actually fix the operating systems and third-party libraries to accept pointer-and-size strings in all places, removing the need for null termination to exist, and for null termination propagation to be relevant. Sometimes, in particular from environments where this may not be unrealistic in a subset, the argument is voiced that cstring_view does not offer any benefits.

Adding the type complicates the type system around strings by having more options for string types. This is not as much of an argument as it seems, since for each site a single type is the most appropriate, and the only places that would differ are those where the difference can make a meaningful performance impact - ie, the exact kind of tight loop where the type is useful for being able to make the difference.

6 Design rationale

6.1 Use cases

We can see two common and perfectly valid use cases where a developer might want to use cstring_view. Both are quite different, and have different requirements. We believe our proposal can satisfy both of them.

6.1.1 String buffer + cstring_view to read from it

In this scenario, the following flow in this order happens:

A very simplified version of such code could look like this:

#include <random> #include <string> #include <iostream> void generate_word(std::string& buffer, std::default_random_engine& random_engine){ std::uniform_int_distribution<int> word_size_distribution(0, buffer.size()); std::uniform_int_distribution<int> character_distribution('A', 'Z'); int word_size = word_size_distribution(random_engine); for(int i = 0; i < word_size; i++){ buffer[i] = character_distribution(random_engine); } if(word_size < buffer.size()){ buffer[word_size] = '\0'; } } int main(){ std::random_device r; std::default_random_engine engine(r()); std::string buffer(10, 'z'); std::cstring_view random_word(buffer.data(), buffer.size()); for(int i = 0; i < 10; i++){ generate_word(buffer, engine); // Next line could be in a place where it does not know anything about the buffer and only has the cstring_view
// Instead of cout it could be provided to a C api that only accepts const char *
std::cout << random_word.c_str() << '\n'; } }

6.1.2 cstring_view as a replacement to const char *

const char * for a null-terminated C-style string has several disadvantages. Because of that, developers migrate to types with better type safety guarantees. Still at some point of the stack, a lot of C++ programs need to interop with C or the POSIX API. To do that, they need to convert this better type back/from const char*.

6.2 Principles

These principles are in strict order of importance according to the paper authors. All implementation choices below should reflect these principles.

1. It must be a non-allocating type that provides a type-system propagating annotation of having a null terminator at the end.

2. The class maintains the invariant that, unless the characters are modified externally, it contains a null terminator just after the last string character. If some operation, such as remove_suffix, would result in violating this invariant, the design should cause a compile error.

3. Type works identical to string_view and string where possible, similar where feasible and fail to compile if neither of those can be done.

4. Principle of least surprise: Type fits between string and string_view. Calling functions that preserve null terminator return cstring_view, functions that do not return string_view. Converting from string to string_view gives the same result as conversion from string to cstring_view and on to string_view.

5. Type works in the way clients of the type will expect, in that size() and strlen(c_str()) are guaranteed to give the same result.

6.3 cstring_view and string_view relation

cstring_view should be transparently convertible to a string_view. There are multiple ways to create this conversion:

1. Implementing cstring_view as a subclass of string_view

2. Implementing string_view as subclass of cstring_view

3. Adding a conversion operator from cstring_view to string_view

4. Adding a constructor to string_view from cstring_view

For 1 and 2, the main question is if either of these types has a set of properties that is a strict subset of the other (ie, LSP). cstring_view has a guarantee that string_view does not uphold, so string_view cannot inherit publicly from cstring_view. However, string_view offers an operator= from string_view, which cstring_view cannot support, so cstring_view cannot inherit publicly from string_view either. That leaves the latter two strategies, and preference is given to the constructor approach.

This brings into reality an overload ambiguity:

void handle(string_view v); void handle(cstring_view v); handle("Hello World!");

This is now an ambiguous function call. It is not a new problem; Example on Godbolt shows that the exact same problem already happens with regular std::string and std::string_view. The reason is somewhat fundamental; all three types model the concept "string" and *are* ambiguous. The user should be clear about which properties of string it's expecting. Adding this new type only adds to the vocabulary the option to say "non-owning null-terminated", giving the user better choices rather than complicating it.

6.3.1 Deprioritizing one overload

When one overload is preferred, but others should exist, the less preferred ones can be converted to a template and marked with requires true. This lifts one overload up out of the overload set for ambiguous matches, while leaving the rest to exist.

6.3.2 Tag type forced conversions for exact matches

https://godbolt.org/z/8qssnq8rf and https://godbolt.org/z/xG7955hf9 illustrate the idea of using a wrapping template to make one overload stand out.

6.3.3 Explicitly adding a function to disambiguate manually

https://godbolt.org/z/9zoGEh1cv demonstrates adding an overload that matches anything that isn't an exact match, which indirects to a function that hand-picks an overload.

6.4 Construction

std::basic_string_view offers a handful of constructors. The principle behind which to support is that if we can match the interface of string, we should, and if not, if we can support the behavior of string_view we should.

constexpr basic_cstring_view(); // (0)

Creates a cstring_view that refers to a static null terminator. data() and c_str() are valid to call and result in a zero-length string, size() returns zero. Behavior matches string.

constexpr basic_cstring_view(const charT* str); // (1)

The basic constructor from an unadorned charT* risks taking a non-null-terminated string and searching for the null terminator. For cstring_view this constructor is as safe as it is for string and string_view, since we need to have a null terminator anyway.

constexpr basic_cstring_view(const charT* str, size_type len); // (2)

This constructor conceptually offers a O(1) construction time. For cstring_view we add a contract that asserts that str[len] == '\0'. The behavior is logically identical to string except that we require strlen to be dereferenceable and to dereference to a nul terminator.

template<class It, class End> constexpr basic_cstring_view(It begin, End end); // (3)

This constructor looks more difficult to support, but string_view's requirements on this constructor make it possible to support in the same fashion. From the action of creating a cstring_view we get the implication from the user that the iterators are contiguous and point to a sized range, and that it must be nul-terminated. We specify the contract that *(begin + (end - begin)) == '\0', and that the contiguous range specified from begin must be dereferenceable in [begin ... (begin + (end - begin))].

template<ntcts R> constexpr explicit basic_cstring_view(R&& r); // (4)

This constructor has the same implication as (3) in that the range passed in must end with a nul terminator. The ability to check data()[size()] is rare, but is specified on string. If we have any other range though, they are always specified in being dereferenceable only from 0 to size()-1, but not at size(). We keep this constructor with a requirement on the type R that it has a c_str function.

basic_cstring_view( std::nullptr_t ) = delete; // (5)

We should not support constructing a string from nullptr. This constructor already exists in string_view.

6.5 Member functions on `cstring_view` that return a `string_view`

In some cases the functions can be replicated on cstring_view with the return type being a cstring_view, while in some cases the return type loses the ability to guarantee null termination, and should still return a string_view. The types are entangled. Note that the user can always treat the return values as-if they were string_view as the type is implicitly convertible; this is strictly giving the user more expressivity and stronger guarantees rather than less.

6.5.1 `substr`

The substr function is changed from a single function with a default argument, to two functions, one with one and one with two arguments. The one-argument substr always retains the end (identical to string_view's substr) and returns a cstring_view; the two-argument substr at least in a conceptual sense chops off at least the null terminator from the end, and should always return a string_view. If the user cares about this difference they can assign to a cstring_view and get a compile error if their call is incompatible. If they assign to a string_view the behavior is identical to what string_view would have done.

6.5.2 `subview`

The subview function is newly added in the 2025 Sofia meeting, which always creates a view on a part of the input string. In this paper we treat it the same as the substr function and create a cstring_view if we can maintain the null termination guarantee, or create a string_view if we cannot statically maintain the guarantee. There is an adjoint paper (P3862) that proposes to fix this in the existing-but-unpublished C++26 standard to avoid future deviation in having std::string::subview return string_view for subview(n).

6.5.3 `remove_prefix`

remove_prefix drops characters from the front of the string it's called on. Behavior fits.

6.5.4 `remove_suffix`

remove_suffix changes the string in-situ, and as such is unimplementable on cstring_view. It should be a function marked as =delete with a reason pointing users to use the two-argument substr function instead, which returns a string_view.

6.6 Embedded NUL bytes

The practice of having embedded NUL bytes in a string or string_view highly depends on the use case. Let's analyze the use cases mentioned in the sections above to understand how likely is this to happen.

6.6.1 Use cases

6.6.1.1 String buffer + cstring_view to read from it

If we look at the string, it is perfectly normal to have embedded NUL bytes during the execution of the program. If we look at the cstring_view, what we want is to get the content until the first NUL byte and consider this first NUL byte as the null terminating character.

6.6.1.2 cstring_view as a replacement to const char *

If cstring_view is constructed only using the pointer information, it cannot contain embedded NUL bytes unless the string that cstring_view is pointing to is modified.

If cstring_view is constructed using the pointer information and the length, then it could contain embedded NUL bytes even though in practice we know that this is very unlikely. In the unlikely case this would happen, then the corresponding C or POSIX API would only read until the first NUL byte due to being considered the null terminating character. This is something that might be surprising for the user, specially if they were not in control of the content.

6.6.2 What can we do with embedded NUL bytes?

Conceptual reasons for not disallowing embedded NUL bytes:

Practical reasons for not disallowing embedded NUL byte:

Reasons for not allowing embedded NUL bytes:

6.6.3 Proposal for embedded NUL bytes

The question was asked in Croydon whether the paper should be changed to *actively reject* embedded \0, and that did not get consensus. The vote was split however - 3/5/2/5/10. Depending on who was asked, embedded NULs are either a security hazard that must be guarded against, an uncommon scenario that would reduce performance to check, or a must-support use case. This explains the spread.

We propose to manage concerns about embedded NUL bytes at the level of values (not types), by providing a suitable API. A potential set of functions for this purpose is as follows:

Notice that the proposal is for c_str() to have a precondition and not a hardened precondition. This means that if the precondition is not fulfilled, then the call to c_str() is undefined behavior. However, the fact that it is a precondition, it allows tools like sanitizers to check for it and diagnose it. It cannot be a hardened precondition as it would make c_str() run in O(n) instead of O(1) breaking its performance contract, which hardened preconditions may not do.

In a codebase where it is important to avoid and detect embedded NUL bytes, a user can use guaranteed_without_nulls() if it just wants to enforce, or it can check has_nulls() to detect it programmatically.

6.7 Lazy size calculation?

The type is used to carry the type-wise annotation that an existing allocated string is null-terminated to a place that needs this information. string and string_view eagerly calculate the size and store it. Both of these types are typically used in C++-style usage and need the size to check bounds, as well as to provide the user with size information with the size() function.

For cstring_view, it can be argued that the main use of the type is to eventually still have the type-wise knowledge that c_str() exists and gives a null-terminated string, and that use as a C++-style string is secondary. In that same argument, eager calculation of size() is of no value, since the user expects only to call c_str() eventually, getting less or no benefit from the size() calculation other than the safety guarantee that on construction the value was null-terminated.

To enable this, we could change the (1) constructor to be O(1) and to set the internal size to a static invalid value. The size() function would change to amortized O(1) and on first call it would store the calculated size in the member value. All other functions that rely directly or indirectly on size() being O(1) would change to being amortized O(1).

Doing so, we would lose the ability to put (hardened) preconditions that have anything to do with the size on any function. The constructor wouldn't be able to check that it's indeed a valid range with a null terminator. operator[] can check that it's within range in a hardened precondition and has to be demoted to amortized O(1), or it stays O(1) and we cannot add that precondition as hardened.

Computing the size() at construction gives an additional safety guarantee: the eagerly-computed size can be used as a hard limit for string iteration, and enables checking that the buffer has not been compromised, thus limiting the risk for unbounded string iterations (that are a common sources of CVEs - Common Vulnerabilities and Exposures).

The change seems strongly related to whether the language we're writing is C or C++ first. If we're writing C, then we should be opting for the direction that does not calculate size eagerly, and potentially not at all. If we're writing C++, the solution in line with what C++26 has added for hardened preconditions is to accept the size calculation. Users that really cannot afford that are still able to use const char*.

6.8 Conversion from string being explicit?

The rationale for this would be that a string can contain embedded NULs, and this would make explicit that we're switching to a type that really doesn't like those. The downside of this is that we are introducing needless friction into the most common usage path, where the vast majority of users never use strings with embedded NULs.

6.9 Modifying the cstring_view or underlying storage

The type talks about having a "guarantee" of null termination, or an "invariant" that the string is null-terminated. These are guarantees within what it can control, which as a view type does not include the underlying storage. Considering this in a Rust-style borrow setup, it would need to borrow the underlying storage while existing to uphold these guarantees. Lacking support for such construct in C++, we cannot encode it into the type nor enforce it. The only thing the type attempts to do is to carry the knowledge of null termination existing at the location size() at construction, to null termination existing at size() where the value is used, given no change of the underlying storage.

string_view itself does not allow modifying the values through its interface and cstring_view should not deviate from this. string_view states that the underlying storage must outlive the string_view built on it, and cstring_view does the same. cstring_view also requires that the null terminator that it requires may not be modified. Beyond that cstring_view and string_view are intentionally alike.

6.10 Standard Library Changes

There are a handful of interfaces in the standard library which take const string& and really want a cstring_view or possibly a string_view. These include:

There are further interfaces which take const char* and could have overloads taking a cstring_view or string_view.

We do not propose any such changes in this paper but would like to investigate this in a follow-up paper.

6.11 Comparison to existing cstring_view implementations

As this is not a novel type, many existing implementations are to be found on Github. These were found by searching for "class cstring_view", "struct cstring_view", "class zstring_view" and "struct zstring_view", filtering out duplicates by hand. This is a nonexhaustive list of public implementations, annotated by their implementation approach and some notes on them. These can be categorized roughly as follows. The column "Replaceable with P3655" indicates N/A if the type doesn't attempt to act like a C++ string_view, No if there are reasons why it would be definitely worse, Maybe if there are some ways it's worse but most that make it better, Yes if it's just a simplification with no visible downsides, and Yes* if there are some features or properties of the type that this paper does not reproduce with the same name.

Repository name Type Notes Replaceable with P3655
helderTZ/cstring_view.h C impl C implementation of cstring_view N/A
REDl3east/cstring_view C impl C implementation of string_view. Yes, confusing name. N/A
VeriFIT/z3-noodler Private uint32_t* and uint32_t Nothing like C++'s std::string N/A
HUD-Software/core Only private char* Internally conflicted in what it wants to be; only compiles for 8-bit types but has is_utf16 and is_utf32 functions that can never work No
fmtlib/fmt Public char* wrapper Wrapper around const char* only, nonexistent interface No
hipony/enumerate Private char* Very limited interface No
schrodinger/pymol-open-source Private char* Full interface, breaks performance guarantees expected Maybe
firebuild/firebuild Public char* and size_t, no functions Notes that it could be replaced with std::cstring_view Maybe
mag7ster/serenedb Public string_view base Known broken Yes*
refi64/zypak Public string_view base Known broken Yes*
deepin-community/bpftrace Public string_view base Known broken Yes
userver-framework/userver Public string_view base Known broken Yes
cataphract/phpext-cpp Public string_view base Known broken, limited interface Yes
Ishu-Kansal/searchengine Two private char*s, partial interface Compatible with P3655 Yes
breyerml/cstring_view Full, private string_view C++ implementation according to P1402, same approach Yes
bemanproject/cstring_view Full, private string_view C++ implementation for P3655, this paper Yes
facebook/folly Full, private string_view C++ implementation almost identical to this paper Yes
ArnavT005/cstring-lite Private string_view Limited interface Yes*
tuccio/oblo Private string_view base Clean simple implementation, very similar Yes
DarkSharpness/network Private string_view Non-template, only for char. Similar in concept Yes
openMSX/openMSX Private char* and size_t Non-template implementation, only for char. Limited interface Yes
patricia-gallardo/cpp-doom Private char* and size_t Very simple, likely implemented in YAGNI style Yes
bmanga/freetype-gl-cpp Private char* and size_t Non-template implementation, only for char. Adds array constructor. Yes*
jdmclark/jkgfxmod Private char* and size_t Limited interface, adds array constructor Yes
jeremy-rifkin/cpptrace Private char* and size_t Non-template implementation, only for char. Limited interface Yes
HappyFaceFriend/ramensoup Private string_view base Some room for improvement (substr, remove_prefix) Yes
tom42/agbpack Private string_view No interface at all? Truncates std::string with nulls Yes*
jamisonrobey/itch-mold-replay Private string_view Limited interface Yes
vab9000/MapProjects Private string_view Non-template; Limited interface Yes*
wallix/redemption Private char* and size_t Non-template, limited interface, more restrictive Yes*
joeri-hu/innovation Private string_view base Non-template, does not restrict or check Yes

Of the implementations we can find publicly on Github called cstring_view, we find that about two-thirds are doing exactly what we propose. Three have a known implementation defect by inheriting publicly, and some others implement only for char or 8-bit types, or leave out many functions on the string_view interface (not intentionally - likely as a way to limit implementation effort more than anything else).

Two main approaches are found that differ from this proposal. One is C libraries implementing either string_view or cstring_view, both under the name cstring_view. This paper does not address C, so these are not relevant.

The other is libraries wrapping only a const char*, with the intent of not calculating size() ahead of time, and only creating a tagged const char* that's known to be intended as a null-terminated string. This paper does not propose a type that does this. Only two public libraries are implementing this approach.

6.11.1 Note on `*` adorned Yes

The type has some functionality or functions beyond what P3655 offers, but can be replaced; either by rewriting the code that uses them, or changing the cstring_view implementation to inherit publicly from std::string_view allowing the existing uses to keep working. In many cases these are helper functions, the array constructor that we address elsewhere in the paper, functions to check utf8 validity, or similar functions to add functionality. In many cases these functions are separate from what string_view and string provide, and would in context of WG21 be considered novel additions, and likely better off as free functions or in separate proposals.

6.11.2 Known broken public inheritance

These would allow the following code sequence (as one example):

cstring_view csv = "abcdef"; string_view& sv = csv; // allowed because public inheritance
sv.remove_suffix(4); // allowed, because string_view has this as available function
// at this point csv refers to the string "ab" that does not null terminate

More of these exist; this is just one example on one unfixable breakage with public inheritance.

6.12 Reference Implementation

A reference implementation is at Beman Project on Github.

7 Historically relevant information

7.1 Bikeshedding

Null-terminated string view types are typically named zstring_view (N3921, P3081, GSL) or cstring_view (P1402). cstring_view follows the std::string::c_str() nomenclature while zstring_view has some establishment and recognizability.

Github code search shows similar popularity between `cstring_view` (1.2k results as of the time of writing) and `zstring_view` (680 results as of the time of writing). In refreshing this paper, it appears that cstring_view has gained ~200 added results, while zstring_view only added 8, hinting at a popular preference for the former name. In the October 2025 update, cstring_view has gained another 300 results, with zstring_view gaining 208.

The paper proposes cstring_view, as it has minor number advantage in every measurement so far.

Refreshing the numbers for the names in June 2026 gives us 8.5k for cstring_view and 1.1k for zstring_view.

7.2 Prior Polls

7.2.1 April 2025, Online SG16

7.2.1.1 Poll 1: P3655R0: No objection to use of std::char_traits for consistency and compatibility with std::string_view.

Attendees: 8 (no abstentions)

7/1/0/0/0

Strong consensus.

7.2.1.2 Poll 2: P3655R0: Forward to LEWG with encouragement to add analysis of overload resolution and techniques to address ambiguity.

Attendees: 8 (no abstentions)

8/0/0/0/0

Strong consensus.

7.2.2 June 2025, Sofia SG23

7.2.2.1 11.4 We want a separate bounded array constructor (prioritized wrt char)*

6/5/0/0/1

Consensus

7.2.2.2 11.5 We want to forbid constructing/assigning zstring_view containing embedded null chars

3/6/0/0/3

Weak consensus

7.2.2.3 We want to drop the first constructor from zstring_view in this paper taking the char*)*

2/3/3/0/2

No consensus

7.2.3 June 2025, Sofia LEWG

7.2.3.1 POLL: We want to spend more time on `zstring_view`

23/8/5/0/0

Attendance: 30 (IP) + 11 (R)

Author’s Position: 2xSF

Outcome: strong consensus in favour

7.2.3.2 POLL: Rename `zstring_view` to `cstring_view`

8/6/13/4/2

Attendance: 30 (IP) + 11 (R)

Author’s Position: F, A

Outcome: No consensus.

7.2.3.3 POLL: `zstring_view` should be disallowed to have NUL characters in the middle.

2/6/10/4/11

Attendance: 27 (IP) + 11 (R)

Author’s Position: F, A

Outcome: No consensus for change

7.2.4 March 2026, Croydon LEWG

7.2.4.1 We would like to see array constructors in this paper for cstring_view before forwarding.

2/3/7/8/5

Attendance: 25 (IP) + 9 (R)

Author’s Position: A, N

Outcome: Weak consensus against

7.2.4.2 We would like to remove the ctor that takes (char *) (only) from the proposal for now. (due to safety concerns)

1/5/1/8/9

Attendance: 27 (IP) + 8 (R)

Author’s Position: N, A

Outcome: Consensus against (no change)

7.2.4.3 We want cstring_view to not support internal ‘\0’ in the type.

3/5/2/5/10

Attendance: 25 (IP) + 10 (R)

Author’s Position: N, A

Outcome: Consensus against (no consensus for a change)

7.3 NVIDIA Experience (moved from p3710)

NVIDIA implemented cstring_view independently, with almost identical features. This is described in P3710 zstring_view: a string_view with guaranteed null termination, now merged into this paper. We also implemented some additional features described in P3566 You shall not pass char*.

Ideally we wanted our input parameters to all be string_view and all the output parameter to be cstring_view (which gives more flexibility).

In practice, there are exceptions on both sides:

Nonetheless, having a large codebase, we knew we couldn't just rewrite the entire codebase to apply this change, so we used cstring_view as a tool for steering our codebase in the ideal direction described above.

Initially, we used as cstring_view as a drop-in replacement for const char* parameters and return values of our APIs, without worrying too much wether or not the function expects a null terminator. As a sidenote, for this migration we asked our developers to be intentional in using .data() or .c_str(), and use .data() when a null-terminator is not expected, and only use .c_str() when the string is required to be terminated. If respected, this rule allows us to just try changing a parameter from cstring_view to string_view and verify wether or not a function is relying on some parameter to be null-terminated. This operation can be done incrementally, one function at a time.

Once this was done, we started from the "deepest" functions to analyze their usage, and replace their parameters cstring_view to string_view, or rewrite them to allow for string_view parameters. We proceeded upwards in the call chain, and did the same. Again, this can be done in multiple passes.

What we observed is that, in some case, this might not only make the code safer, but also enable new optimizations.

Here follows an example of a parameter upgrade how we implemented these changes, step-by-step, with an example of how this can lead to more performant and safer code at the end of the process.

void f(const char* x) { external_library::g1(x); } void f1() { std::string a1 = "test"; f(a1.c_str()); } void f2() { constexpr char a2[] = "test"; f(a2); } void f3() { const char* a3 = "test"; f(a3); } void f4() { string_view a4 = "abcde"; string_view subtext = a4.substr(2, 2); // no null terminator at the end of `subtext`
f(string{subtext}); // create a temporary to add the terminator
}

In this example, external_library::g1 is a placeholder for some generic computation that happens on the string.

Initially, we update the API we will change f to:

void f(cstring_view x) { external_library::g1(x.c_str()); }

All the functions now have well-defined memory ranges (no unbounded string).

We can now then look for alternatives, and find out there's a different API for the external_library::g1 we can use, e.g. external_library::g2(char*, size_t), that doesn't require a null-terminated sequence.

void f(string_view x) { external_library::g2(x.data(), x.size()); }

As next step, we can check all the usages, and see if we can get rid of some extra code, for example:

void f4() { string_view a4 = "abcde"; string_view subtext = a4.substr(2, 2); // no null terminator at the end of `subtext`
f(subtext); // f now accepts a string_view, no need for temporary
}

This results in a better-optimized code at the end (less need for temporary strings).

NOTE: The sequence of changes in our codebase was different, because our codebase includes the changes proposed in P3566 You shall not pass char*, so some of the intermediate steps relying on implicit char* -> string_view and char* -> cstring_view conversions require additional changes, and intermediate steps marking conversions with the proposed unsafe_length tag.

8 Wording

Changes in [format.formatter.spec] and [string.view.general] are deltas, all subsequent are full additions.

8.1 [format.formatter.spec]

Add to 2.2:

template<class traits> struct formatter<basic_cstring_view<charT, traits>, charT>;

Add to 4.1:

template<class traits> struct formatter<basic_cstring_view<char, traits>, wchar_t>;

8.2 Concept c-string-holder [concept.ntcts]

template<typename T> concept c-string-holder = ranges::contiguous_range<T> && ranges::sized_range && requires(const T & t) { typename T::value_type; { t.c_str() } -> std::same_as<const typename T::value_type*>; requires is_same_v<ranges::range_value_t<T>, typename T::value_type>; };

Given a t of type const T&, T models c-string-holder only if t.c_str() points to an NTCTS (defns.ntcts).

8.3 String View Classes [string.view]

8.3.1 General [string.view.general]

Update as indicated:

The class template basic_string_view describes an object that can refer to a constant contiguous sequence of char-like ([strings.general]) objects with the first element of the sequence at position zero. The class template basic_cstring_view describes an object that can refer to a constant contiguous sequence of char-like ([strings.general]) objects with the first element of the sequence at position zero, that is guaranteed to contain a null-terminator at the position size(). In the rest of [string.view], the type of the char-like objects held in a basic_string_view or a basic_cstring_view object is designated by charT.

For basic_string_view, [data(), data() + size()) is a valid range. For basic_cstring_view, [data(), data() + size()] is a valid range and data() + size() points at an object with value charT() (a "null terminator").

[Note: The library provides implicit conversions from const charT* and std::basic_string<charT, ...> to std::basic_cstring_view<charT, ...>, and implicit conversions from const char*, std::basic_string<charT, ...> and std::basic_cstring_view<charT, ...> to std::basic_string_view<charT, ...> so that user code can accept just std::basic_string_view<charT> or std::basic_cstring_view<charT> as a non-templated parameter wherever a sequence of characters is expected. User-defined types can define their own implicit conversions to std::basic_string_view<charT> or std::basic_cstring_view<charT> in order to interoperate with these functions. — end note]

8.3.2 Header cstring_view [string.view.cstring]

namespace std { template<typename T> concept ntcts = requires(const T & t) { { t.c_str() } -> std::same_as<const T::value_type*> }; // [string.view.cstring_view.template], class template basic_cstring_view
template<class charT, class traits = char_traits<charT>> class basic_cstring_view; // partially freestanding
template<class charT, class traits> constexpr bool ranges::enable_view<basic_cstring_view<charT, traits>> = true; template<class charT, class traits> constexpr bool ranges::enable_borrowed_range<basic_cstring_view<charT, traits>> = true; // [string.view.cstring_view.comparison], non-member comparison functions
template<class charT, class traits> constexpr bool operator==(basic_cstring_view<charT, traits> x, type_identity_t<basic_cstring_view<charT, traits>> y) noexcept; template<class charT, class traits> constexpr /* see below */
operator<=>(basic_cstring_view<charT, traits> x, type_identity_t<basic_cstring_view<charT, traits>> y) noexcept; // [string.view.cstring_view.io], inserters and extractors
template<class charT, class traits> basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& os, basic_cstring_view<charT, traits> str); // hosted
// basic_cstring_view typedef-names
using cstring_view = basic_cstring_view<char>; using u8cstring_view = basic_cstring_view<char8_t>; using u16cstring_view = basic_cstring_view<char16_t>; using u32cstring_view = basic_cstring_view<char32_t>; using wcstring_view = basic_cstring_view<wchar_t>; // [string.view.cstring_view.hash], hash support
template<class T> struct hash; template<> struct hash<cstring_view>; template<> struct hash<u8cstring_view>; template<> struct hash<u16cstring_view>; template<> struct hash<u32cstring_view>; template<> struct hash<wcstring_view>; inline namespace literals { inline namespace cstring_view_literals { // [string.view.cstring_view.literals], suffix for basic_cstring_view literals
constexpr cstring_view operator""csv(const char* str, size_t len) noexcept; constexpr u8cstring_view operator""csv(const char8_t* str, size_t len) noexcept; constexpr u16cstring_view operator""csv(const char16_t* str, size_t len) noexcept; constexpr u32cstring_view operator""csv(const char32_t* str, size_t len) noexcept; constexpr wcstring_view operator""csv(const wchar_t* str, size_t len) noexcept; } } }

8.3.2.1 Class template basic_cstring_view [string.view.cstring.template]

8.3.2.1.1 General [string.view.cstring.template.general]
namespace std { template<class charT, class traits = char_traits<charT>> class basic_cstring_view { public: // types
using traits_type = traits; using value_type = charT; using pointer = value_type*; using const_pointer = const value_type*; using reference = value_type&; using const_reference = const value_type&; using const_iterator = implementation-defined; // see [string.view.cstring.iterators]
using iterator = const_iterator; using const_reverse_iterator = reverse_iterator<const_iterator>; using reverse_iterator = const_reverse_iterator; using size_type = size_t; using difference_type = ptrdiff_t; static constexpr size_type npos = size_type(-1); // [string.view.cstring.cons], construction and assignment
constexpr basic_cstring_view() noexcept; basic_cstring_view(const basic_cstring_view&) noexcept = default; basic_cstring_view& operator=(const basic_cstring_view&) noexcept = default; constexpr basic_cstring_view(const charT* str) noexcept; constexpr basic_cstring_view(const charT* str, size_type len) noexcept; template<class It, class End> constexpr basic_cstring_view(It begin, End end); template<ntcts R> constexpr basic_cstring_view(R&& r); // [string.view.cstring.iterators], iterator support
constexpr const_iterator begin() const noexcept; constexpr const_iterator end() const noexcept; constexpr const_iterator cbegin() const noexcept; constexpr const_iterator cend() const noexcept; constexpr const_reverse_iterator rbegin() const noexcept; constexpr const_reverse_iterator rend() const noexcept; constexpr const_reverse_iterator crbegin() const noexcept; constexpr const_reverse_iterator crend() const noexcept; // [string.view.cstring.capacity], capacity
constexpr size_type size() const noexcept; constexpr size_type length() const noexcept; constexpr size_type max_size() const noexcept; [[nodiscard]] constexpr bool empty() const noexcept; // [string.view.cstring.access], element access
constexpr const_reference operator[](size_type pos) const; constexpr const_reference at(size_type pos) const; constexpr const_reference front() const; constexpr const_reference back() const; constexpr const_pointer data() const noexcept; constexpr const_pointer c_str() const noexcept; // [string.view.cstring.modifiers], modifiers
constexpr void remove_prefix(size_type n); constexpr void remove_suffix(size_type n) = delete("cannot remove_suffix in-place on cstring_view while retaining null terminator. Use substr instead."); constexpr void swap(basic_cstring_view& s) noexcept; // [string.view.cstring.ops], cstring operations
constexpr size_type copy(charT* s, size_type n, size_type pos = 0) const; constexpr basic_cstring_view<charT, traits> substr(size_type pos = 0) const; constexpr basic_cstring_view<charT, traits> subview(size_type pos = 0) const; constexpr basic_string_view<charT, traits> substr(size_type pos, size_type n) const; constexpr basic_string_view<charT, traits> subview(size_type pos, size_type n) const; constexpr int compare(basic_string_view<charT, traits> s) const noexcept; constexpr int compare(size_type pos1, size_type n1, basic_string_view s) const; constexpr int compare(size_type pos1, size_type n1, basic_string_view s, size_type pos2, size_type n2) const; constexpr int compare(const charT* s) const; constexpr int compare(size_type pos1, size_type n1, const charT* s) const; constexpr int compare(size_type pos1, size_type n1, const charT* s, size_type n2) const; constexpr bool starts_with(basic_string_view<charT, traits> x) const noexcept; constexpr bool starts_with(charT x) const noexcept; constexpr bool starts_with(const charT* x) const; constexpr bool ends_with(basic_string_view<charT, traits> x) const noexcept; constexpr bool ends_with(charT x) const noexcept; constexpr bool ends_with(const charT* x) const; constexpr bool contains(basic_string_view<charT, traits> x) const noexcept; constexpr bool contains(charT x) const noexcept; constexpr bool contains(const charT* x) const; // [string.view.cstring.find], searching
constexpr size_type find(basic_string_view<charT, traits> s, size_type pos = 0) const noexcept; constexpr size_type find(charT c, size_type pos = 0) const noexcept; constexpr size_type find(const charT* s, size_type pos, size_type n) const; constexpr size_type find(const charT* s, size_type pos = 0) const; constexpr size_type rfind(basic_string_view<charT, traits> s, size_type pos = npos) const noexcept; constexpr size_type rfind(charT c, size_type pos = npos) const noexcept; constexpr size_type rfind(const charT* s, size_type pos, size_type n) const; constexpr size_type rfind(const charT* s, size_type pos = npos) const; constexpr size_type find_first_of(basic_string_view<charT, traits> s, size_type pos = 0) const noexcept; constexpr size_type find_first_of(charT c, size_type pos = 0) const noexcept; constexpr size_type find_first_of(const charT* s, size_type pos, size_type n) const; constexpr size_type find_first_of(const charT* s, size_type pos = 0) const; constexpr size_type find_last_of(basic_string_view<charT, traits> s, size_type pos = npos) const noexcept; constexpr size_type find_last_of(charT c, size_type pos = npos) const noexcept; constexpr size_type find_last_of(const charT* s, size_type pos, size_type n) const; constexpr size_type find_last_of(const charT* s, size_type pos = npos) const; constexpr size_type find_first_not_of(basic_string_view<charT, traits> s, size_type pos = 0) const noexcept; constexpr size_type find_first_not_of(charT c, size_type pos = 0) const noexcept; constexpr size_type find_first_not_of(const charT* s, size_type pos, size_type n) const; constexpr size_type find_first_not_of(const charT* s, size_type pos = 0) const; constexpr size_type find_last_not_of(basic_string_view<charT, traits> s, size_type pos = npos) const noexcept; constexpr size_type find_last_not_of(charT c, size_type pos = npos) const noexcept; constexpr size_type find_last_not_of(const charT* s, size_type pos, size_type n) const; constexpr size_type find_last_not_of(const charT* s, size_type pos = npos) const; // [string.view.cstring.null], embedded null management
constexpr bool has_nulls() const noexcept; constexpr cstring_view first_piece() const noexcept; constexpr cstring_view& guaranteed_without_nulls() const noexcept; private: const_pointer data_; // exposition only
size_type size_; // exposition only
}; }

8.3.2.2 Construction and assignment [string.view.cstring.cons]

constexpr basic_cstring_view() noexcept;

Effects: Constructs an empty basic_cstring_view.

Postconditions: size() is 0 and data() points to a valid empty string

Complexity: O(1).

constexpr basic_cstring_view(const charT* str) noexcept;

Preconditions: [str, str + traits::length(str)] is a valid range, str[traits::length(str)] == charT()

Effects: Constructs a basic_cstring_view that refers to str.

Postconditions: size() returns traits::length(str) and data() returns str.

Complexity: O(traits::length(str)).

constexpr basic_cstring_view(const charT* str, size_type len) noexcept;

Preconditions: [str, str + len] is a valid range, str[len] == charT().

Effects: Constructs a basic_cstring_view that refers to str.

Postconditions: size() returns len and data() returns str.

Complexity: O(1).

template<class It, class End> constexpr basic_cstring_view(It begin, End end);

Constraints:

— It satisfies contiguous_iterator.

— End satisfies sized_sentinel_for<It>.

— is_same_v<iter_value_t<It>, charT> is true.

— is_convertible_v<End, size_type> is false.

Preconditions:

— [begin, end] is a valid range.

— It models contiguous_iterator.

— End models sized_sentinel_for<It>.

— *(begin + (end - begin)) == charT()

Effects: Creates a basic_cstring_view that refers to the range from begin to end, inclusive.

Postconditions: size() returns (end - begin) and data() returns to_address(begin).

Throws: When and what end - begin throws.

Complexity: O(1).

template<ntcts R> constexpr basic_cstring_view(R&& r);

Let d be an lvalue of type remove_cvref_t<R>.

Constraints:

— remove_cvref_t<R> is not the same type as basic_cstring_view,

— R models ranges::contiguous_range, ranges::sized_range and ntcts

— is_same_v<ranges::range_value_t<R>, charT> is true,

— is_convertible_v<R, const charT*> is false, and

— d.operator ::std::basic_cstring_view<charT, traits>() is not a valid expression.

Effects: Creates a basic_cstring_view that refers to the range specified by r.

Postconditions: size() returns ranges::size(r) and data() returns ranges::data(r).

Throws: Any exception thrown by ranges::data(r) and ranges::size(r).

8.3.2.3 Deduction guides [string.view.cstring.deduct]

template<class It, class End> basic_cstring_view(It, End) -> basic_cstring_view<iter_value_t<It>>;

Constraints:

— It satisfies contiguous_iterator.

— End satisfies sized_sentinel_for<It>.

template<ntcts R> basic_cstring_view(R&&) -> basic_cstring_view<ranges::range_value_t<R>>;

Constraints: R satisfies ranges::contiguous_range and ntcts

8.3.2.4 Iterator support [string.view.cstring.iterators]

constexpr const_iterator begin() const noexcept; constexpr const_iterator cbegin() const noexcept;

Returns: An iterator such that addressof(*begin()) == data().

constexpr const_iterator end() const noexcept; constexpr const_iterator cend() const noexcept;

Returns: begin() + size().

constexpr const_reverse_iterator rbegin() const noexcept; constexpr const_reverse_iterator crbegin() const noexcept;

Returns: const_reverse_iterator(end()).

constexpr const_reverse_iterator rend() const noexcept; constexpr const_reverse_iterator crend() const noexcept;

Returns: const_reverse_iterator(begin()).

8.3.2.5 Capacity [string.view.cstring.capacity]

constexpr size_type size() const noexcept; constexpr size_type length() const noexcept;

Returns: size_.

constexpr size_type max_size() const noexcept;

Returns: The largest possible number of char-like objects that can be referred to by a basic_cstring_view.

[[nodiscard]] constexpr bool empty() const noexcept;

Returns: size() == 0.

8.3.2.6 Element access [string.view.cstring.access]

constexpr const_reference operator[](size_type pos) const;

Hardened preconditions: pos <= size() is true.

Returns: data()[pos].

Throws: Nothing.

constexpr const_reference at(size_type pos) const;

Returns: data()[pos].

Throws: out_of_range if pos > size().

constexpr const_reference front() const;

Hardened preconditions: empty() is false.

Returns: data()[0].

Throws: Nothing.

constexpr const_reference back() const;

Hardened preconditions: empty() is false.

Returns: data()[size()-1].

Throws: Nothing.

constexpr const_pointer data() const noexcept; constexpr const_pointer c_str() const noexcept;

Precondition: !has_nulls()

Returns: data_.

[Note: The pointer returned is safe to pass to any function expecting a null-terminated string]

Postcondition: The returned value can be dereferenced in [0, size()].

8.3.2.7 Modifiers [string.view.cstring.modifiers]

constexpr void remove_prefix(size_type n);

Hardened preconditions: n <= size() is true.

Effects: Equivalent to: data_ += n; size_ -= n;

constexpr void swap(basic_cstring_view& s) noexcept;

Effects: Exchanges the values of *this and s.

8.3.2.8 String operations [string.view.cstring.ops]

constexpr size_type copy(charT* s, size_type n, size_type pos = 0) const;

Let rlen be the smaller of n and size() - pos.

Preconditions: [s, s + rlen) is a valid range.

Effects: Equivalent to traits::copy(s, data() + pos, rlen).

Returns: rlen.

Throws: out_of_range if pos > size().

Complexity: O(rlen).

constexpr basic_cstring_view<charT, traits> substr(size_type pos = 0) const; constexpr basic_cstring_view<charT, traits> subview(size_type pos = 0) const;

Returns: basic_cstring_view(data() + pos, size() - pos).

Throws: out_of_range if pos > size().

constexpr basic_string_view<charT, traits> substr(size_type pos, size_type n) const; constexpr basic_string_view<charT, traits> subview(size_type pos, size_type n) const;

Let rlen be the smaller of n and size() - pos.

Effects: Determines rlen, the effective length of the string to reference.

Returns: basic_string_view(data() + pos, rlen).

Throws: out_of_range if pos > size().

constexpr int compare(basic_string_view str) const noexcept;

Returns: Equivalent to: return substr().compare(str)

constexpr int compare(size_type pos1, size_type n1, basic_string_view str) const;

Effects: Equivalent to: return substr(pos1, n1).compare(str)

constexpr int compare(size_type pos1, size_type n1, basic_string_view str, size_type pos2, size_type n2) const;

Effects: Equivalent to: return substr(pos1, n1).compare(str.substr(pos2, n2))

constexpr int compare(const charT* s) const;

Effects: Equivalent to: return compare(basic_string_view<charT, traits>(s))

constexpr int compare(size_type pos1, size_type n1, const charT* s) const;

Effects: Equivalent to: return substr(pos1, n1).compare(basic_string_view<charT, traits>(s))

constexpr int compare(size_type pos1, size_type n1, const charT* s, size_type n2) const;

Effects: Equivalent to: return substr(pos1, n1).compare(basic_string_view<charT, traits>(s, n2))

constexpr bool starts_with(basic_string_view<charT, traits> x) const noexcept;

Let rlen be the smaller of size() and x.size().

Effects: Equivalent to: return basic_string_view<charT, traits>(data(), rlen) == x

constexpr bool starts_with(charT x) const noexcept;

Effects: Equivalent to: return !empty() && traits::eq(front(), x)

constexpr bool starts_with(const charT* x) const;

Effects: Equivalent to: return starts_with(basic_string_view<charT, traits>(x))

constexpr bool ends_with(basic_string_view<charT, traits> x) const noexcept;

Let rlen be the smaller of size() and x.size().

Effects: Equivalent to: return basic_string_view(data() + (size() - rlen), rlen) == x

constexpr bool ends_with(charT x) const noexcept;

Effects: Equivalent to: return !empty() && traits::eq(back(), x)

constexpr bool ends_with(const charT* x) const

Effects: Equivalent to: return ends_with(basic_string_view(x))

constexpr bool contains(basic_string_view<charT, traits> x) const noexcept; constexpr bool contains(charT x) const noexcept; constexpr bool contains(const charT* x) const pre (x != nullptr);

Effects: Equivalent to: return find(x) != npos

8.3.2.9 Searching [string.view.cstring.find]

Member functions in this subclause have complexity O(size() * str.size()) at worst, although implementations should do better.

Let F be one of find, rfind, find_first_of, find_last_of, find_first_not_of, and find_last_not_of.

— Each member function of the form

constexpr return-type F (const charT* s, size_type pos) const;

has effects equivalent to: return F (basic_string_view(s), pos);

— Each member function of the form

constexpr return-type F (const charT* s, size_type pos, size_type n) const;

has effects equivalent to: return F (basic_string_view(s, n), pos);

— Each member function of the form

constexpr return-type F (charT c, size_type pos) const noexcept;

has effects equivalent to: return F (basic_string_view(addressof(c), 1), pos);

— Each member function of the form

constexpr return-type F (basic_string_view<charT, traits> s, size_type pos = 0) const noexcept;

has effects equivalent to: return basic_string_view<charT, traits>(*this).F(s, pos);

8.3.3 Non-member comparison functions, [string.view.cstring.comparison]

template<class charT, class traits> constexpr bool operator==(basic_cstring_view<charT, traits> x, type_identity_t<basic_cstring_view<charT, traits>> y) noexcept;

Returns: lhs.compare(rhs) == 0.

template<class charT, class traits> constexpr /* see below */
operator<=>(basic_cstring_view<charT, traits> x, type_identity_t<basic_cstring_view<charT, traits>> y) noexcept;

Returns: Equivalent to: return lhs.substr().operator<=>(rhs);

8.3.4 Embedded null management, [string.view.cstring.null]

constexpr bool has_nulls() const noexcept;

Returns: Whether any of the values in the cstring_view is equal to \0

Complexity: O(n).

constexpr cstring_view first_piece() const noexcept;

Returns: The same string value, terminated at the first \0 inside the string if any, and at the guaranteed null terminator otherwise

Complexity: O(n).

constexpr cstring_view& guaranteed_without_nulls() const noexcept;

Hardened precondition: !has_nulls()

Equivalent to: return *this;

Complexity: O(n).

8.3.5 Inserters and extractors, [string.view.cstring.io]

template<class charT, class traits> basic_ostream<charT, traits>& operator<<(basic_ostream<charT, traits>& os, basic_cstring_view<charT, traits> str);

Returns: Equivalent to: return os << str.substr();

8.3.6 Hash support, [string.view.cstring.hash]

namespace std { template<> struct hash<cstring_view>; template<> struct hash<u8cstring_view>; template<> struct hash<u16cstring_view>; template<> struct hash<u32cstring_view>; template<> struct hash<wcstring_view>; }

The specialization is enabled.

[Note: The hash value of a string view object is equal to the hash value of the corresponding string object [basic.string.hash]. — end note]

8.3.7 Suffix for basic_cstring_view literals, [string.view.cstring.literals]

consteval cstring_view operator""csv(const char* str, size_t len) noexcept;

Returns: cstring_view{str, len}.

consteval u8cstring_view operator""csv(const char8_t* str, size_t len) noexcept;

Returns: u8cstring_view{str, len}.

consteval u16cstring_view operator""csv(const char16_t* str, size_t len) noexcept;

Returns: u16cstring_view{str, len}.

consteval u32cstring_view operator""csv(const char32_t* str, size_t len) noexcept;

Returns: u32cstring_view{str, len}.

```cpp consteval wcstring_view operator""csv(const wchar_t* str, size_t len) noexcept;

Returns: wcstring_view{str, len}.