ISO/IEC JTC1 SC22 WG21 N3849

Date:

Jeffrey Yasskin <[email protected]>

string_view: a non-owning reference to a string, revision 6

Skip table of contents

Overview

References to strings are very common in C++ programs, but often the callee doesn't care about the exact type of the object that owns the data. 3 things generally happen in this case:

  1. The callee takes const std::string& and insists that callers copy the data if it was originally owned by another type.
  2. The callee takes two parameters—a char* and a length (or just char* and assumes 0-termination)—and reduces the readability and safety of calls and loses any helper functions the original type provided.
  3. The callee is rewritten as a template and its implementation is moved to a header file. This can increase flexibility if the author takes the time to code to a weaker iterator concept, but it can also increase compile time and code size, and can even introduce bugs if the author misses an assumption that the argument's contents are contiguous.

Google, LLVM, and Bloomberg have independently implemented a string-reference type to encapsulate this kind of argument. string_view is implicitly constructible from const char* and std::string. It provides the const member operations from std::string to ease conversion. This paper follows Chromium and Bloomberg in extending string_view to basic_string_view<charT>, and further extends it to include a traits parameter to match basic_string. We provide typedefs to parallel the 4 basic_string typedefs.

Both Google's and LLVM's string_view types (but not Bloomberg's) extend the interface from std::string to provide some helpful utility functions:

Versions of std::string operations that take string_view instead also give the standard a way to provide in-place operations on non-null-terminated byte/character sequences:

Inventions in this paper

Google's StringPiece provides as_string and ToString methods to convert to std::string. LLVM's StringRef provides both a str() explicit conversion and an implicit operator std::string(). Since this paper builds on top of C++11, we provide an explicit conversion constructor as well as a less verbose to_string function.

None of the existing classes have constexpr methods.

Bikeshed!

We found rough consensus around renaming this class to string_view. Other options included:

Modifications vs std::string

The interface of string_view is similar to, but not exactly the same as the interface of std::string. In general, we want to minimize differences between std::string and string_view so that users can go back and forth between the two often. This section justifies the differences whose utility we think overcomes that general rule.

Addition

Why not change <my-pet-feature>?

I haven't taken every suggestion to change string_view. This section explains the rationales.

Remove the find*() methods

Many people have asked why we aren't removing all of the find* methods, since they're widely considered a wart on std::string. First, we'd like to make it as easy as possible to convert code to use string_view, so it's useful to keep the interface as similar as reasonable to std::string. Second, replacing these these methods with uses of the standard algorithms library requires switching from indices to iterators, writing somewhat-complicated conversion code, and/or passing custom lambdas to find_if. Let's look at the replacement code for each of the remaining methods:

haystack.find(needle)
Replaced by:
auto iter = std::search(haystack.begin(), haystack.end(),
                        needle.begin(), needle.end());
return iter == haystack.end() ? std::string::npos : iter - haystack.begin();
haystack.rfind(needle)
Replaced by:
auto iter = std::find_end(haystack.begin(), haystack.end(),
                          needle.begin(), needle.end());
return iter == haystack.end() ? std::string::npos : iter - haystack.begin();
haystack.find_first_of(needles)
Replaced by:
auto iter = std::find_first_of(haystack.begin(), haystack.end(),
                               needles.begin(), needles.end());
return iter == haystack.end() ? std::string::npos : iter - haystack.begin();
haystack.find_last_of(needles)
Replaced by:
auto iter = std::find_first_of(haystack.rbegin(), haystack.rend(),
                               needles.begin(), needles.end());
return iter == haystack.rend() ? std::string::npos : iter.base() - 1 - haystack.begin();
haystack.find_first_not_of(straw)
Replaced by:
auto iter = std::find_if(haystack.begin(), haystack.end(), [&](char c) {
  return std::find(straw.begin(), straw.end(), c) == straw.end();
});
return iter == haystack.end() ? std::string::npos : iter - haystack.begin();
haystack.find_last_not_of(straw)
Replaced by:
auto iter = std::find_if(haystack.rbegin(), haystack.rend(), [&](char c) {
  return std::find(straw.begin(), straw.end(), c) == straw.end();
});
return iter == haystack.rend() ? std::string::npos : iter.base() - 1 - haystack.begin();

find, rfind, and find_first_of are straightforward, although the conversion from indices to iterators would prevent many users from switching even to them. find_last_of, find_first_not_of, and find_last_not_of get progressively worse to handle even in an iterator-based function.

Discussion in Bristol concluded that string_view should include all of the const signatures from string.

Bristol straw poll on "Should we keep the pos/n arguments to find, etc?"
SFWFNWASA
54120
Bristol straw poll on "Should we keep the copy() method?"
SFWFNWASA
42232

Make basic_string_view<char> mutable

… and use basic_string_view<const char> for the constant case. The constant case is enough more common than the mutable case that it needs to be the default. Making the mutable case the default would prevent passing string literals into string_view parameters, which would defeat a significant use case for string_view. In a somewhat analogous sitation, LLVM defined an ArrayRef class in Feb 2011, and didn't find a need for the matching MutableArrayRef until Jan 2012. They still haven't needed a mutable version of StringRef. One possible reason for this is that most uses that need to modify a string also need to be able to change its length, and that's impossible through even a mutable version of string_view.

We could use typedef basic_string_view<const char> string_view to make the immutable case the default while still supporting the mutable case using the same template. I haven't gone this way because it would complicate the template's definition without significantly helping users.

Add an explicit operator bool

This would be an abbreviation for !empty(), usable for initialization in if statements. N3509 came to SG9 in Bristol and was not accepted.

Bristol straw poll on "Do we want to pursue [N3509]?"
SFWFWASA
0135

Avoid strlen("string literal")

With a constructor of the form:

template<size_t N>
basic_string_view(const charT (&str)[N]);

we could avoid a strlen() call when a basic_string_view is constructed from a string literal. Unfortunately, this constructor does completely the wrong thing when called like:

char space[PATH_MAX];
snprintf(space, sizeof(space), "some string");
string_view str(space);

It would be possible to avoid that problem by defining a basic_string_view(char* str) that uses strlen() again, but this adds complexity. Some people have suggested a string_view::from_literal method, but I consider that too verbose.

Even the original worry is obsolete given modern optimizers: both gcc and clang optimize strlen("Literal") into a constant, making the simple, safe code as efficient as the template. Other implementations should provide the same optimization as a QoI issue.

Define comparison on begin/end instead of the elements

Operations on string_view apply to the characters in the string, and not the pointers that refer to the characters. This introduces the possibility that the underlying characters might change while a string_view referring to them is in an associative container, which would break the container, but we believe this risk is worthwhile because it matches existing practice and matches user intentions more often.

Wait for contiguous_range<charT>

contiguous_range<T> along with an is_contiguous<IteratorOrRange> trait would be useful for many purposes. However, a reference class that's specifically for strings provides a couple extra benefits:

Make string_view null-terminated

Doing this naively makes substr impossible to implement without a copy. We could imagine inventing a more complex interface that records whether the input string was null-terminated, giving the user the option to use that string when trying to pass a string_view to a legacy or C function expecting a null-terminated const char*. This proposal doesn't include such an interface because it would make string_view bigger or more expensive, and because there's no existing practice to guide us toward the right interface.

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.

s/remove_prefix/pop_front/, etc.

In Kona 2012, I proposed a range<> class with pop_front, etc. members that adjusted the bounds of the range. Discussion there indicated that committee members were uncomfortable using the same names for lightweight range operations as container operations. Existing practice doesn't agree on a name for this operation, so I've kept the name used by Google's StringPiece.

Allow implicit conversion from more types.

Beman Dawes suggested defining std::string_view_{begin,end} and allowing users to add overloads within std. Using ADL is a slight variant. We could also allow conversion from any type with .data() and .size() members returning the right types.

Ultimately, I think we want to allow this conversion based on detecting contiguous ranges. Any constructor we add to work around that is going to look like a wart in a couple years. I think we'll be better off making users explicitly convert when they can't add an appropriate conversion operator, and then we can add the optimal constructor when contiguous iterators make it into the library.

Bristol straw poll on "Should we provide this adaptation method?"
SFWFNWASA
00156

string_view().data() == nullptr?

The obvious way to initialize a default constructed string_view is with .data() == nullptr and .size() == 0. However, that violates an invariant of data(), that it never returns nullptr. Instead, implementations must initialize data() with an arbitrary pointer value such that [data(),data()) is a valid range. (On many platforms, an arbitrary value might suffice, but on some platforms, this might need to be a dereferenceable or one-past-the-end address like this or "".)

We could imagine avoiding this complication by allowing data() to return nullptr, which would also allow us to relax the precondition on string_view(const char*, size_t). However, in Google's implementation, we found that programmers tended to use data()==nullptr to signal conditions that differed from simply empty(). This was a source of confusion in interfaces, and it gives string_view one more possible value than const std::string&, so this proposal forbids the possibility.

Even without relaxing the precondition on string_view(const char*, size_t), allowing implementations to return a null data() from string_view() without requiring it, would cause portability problems: some users would test for data()==nullptr to identify a default-constructed string_view on that implementation, and would then be unable to use another implementation.

However, std::vector::data() may return nullptr, and we don't have contiguous_iterator_tag available yet to allow users to pass a vector's begin() and end() to string_view(), so this requirement may require extra conditions in user code. This and strong interest on the std-proposals mailing list encouraged me to add an alternate wording section below.

Plans for future changes

Paper revision history

This paper updates N3762 by:

N3762 updated N3685 by removing other standard library updates so that the core string_view class can be accepted independently. I've also:

N3685 updated N3609 with the results of the LEWG discussion in Bristol. Significant changes include:

N3609 was a minor update to N3512 that renamed the proposed class to basic_string_view and fixed some wording mistakes.

N3512 updated N3442 with wording for the draft C++14 standard. Note that we still aren't sure whether we're aiming for a TS or C++14.

N3442 was aimed at a TS and updated N3334 by removing array_ref.

The most recent version of this paper is maintained on GitHub.


Wording for the Fundamentals TS

Clause x, string_view

The class template basic_string_view describes objects that can refer to a constant contiguous sequence of char-like (C++11[strings.general]) objects with the first element of the sequence at position zero. In the rest of this Clause, the type of the char-like objects held in a basic_string_view object is designated by charT.

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

The complexity of member functions is O(1) unless otherwise specified.

Add a "Header <string_view> synopsis"

namespace std {
namespace experimental {
inline namespace fundamentals_v1 {
  // [basic.string.view], basic_string_view:
  template<class charT, class traits = char_traits<charT>>
      class basic_string_view;

  // [string.view.comparison], non-member basic_string_view comparison functions
  template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT, traits> x, basic_string_view<charT, traits> y) noexcept;
  template<class charT, class traits>
  constexpr bool operator!=(basic_string_view<charT, traits> x, basic_string_view<charT, traits> y) noexcept;
  template<class charT, class traits>
  constexpr bool operator< (basic_string_view<charT, traits> x, basic_string_view<charT, traits> y) noexcept;
  template<class charT, class traits>
  constexpr bool operator> (basic_string_view<charT, traits> x, basic_string_view<charT, traits> y) noexcept;
  template<class charT, class traits>
  constexpr bool operator<=(basic_string_view<charT, traits> x, basic_string_view<charT, traits> y) noexcept;
  template<class charT, class traits>
  constexpr bool operator>=(basic_string_view<charT, traits> x, basic_string_view<charT, traits> y) noexcept;
  // [string.view.comparison], sufficient additional overloads of comparison functions

  // [string.view.nonmem], other non-member basic_string_view functions
  template<class charT, class traits = char_traits<charT>,
           class Allocator = allocator<charT> >
    basic_string<charT, traits, Allocator> to_string(
      basic_string_view<charT, traits>,
      const Allocator& a = Allocator());

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

  // basic_string_view typedef names
  typedef basic_string_view<char> string_view;
  typedef basic_string_view<char16_t> u16string_view;
  typedef basic_string_view<char32_t> u32string_view;
  typedef basic_string_view<wchar_t> wstring_view;

}  // namespace fundamentals_v1
}  // namespace experimental

  // [string.view.hash], hash support:
  template <class T> struct hash;
  template <> struct hash<experimental::string_view>;
  template <> struct hash<experimental::u16string_view>;
  template <> struct hash<experimental::u32string_view>;
  template <> struct hash<experimental::wstring_view>;
}  // namespace std

The function templates defined in C++11[utility.swap] and C++11[iterator.range] are available when <string_view> is included.

Normally I would update the list in C++11[iterator.range], but we're not yet sure how to do that in a TS, so I picked the more self-contained option.

namespace std {
namespace experimental {
namespace fundamentals_v1 {
  template<class charT, class traits = char_traits<charT>>
  class basic_string_view {
    public:
    // types
    typedef traits traits_type;
    typedef charT value_type;
    typedef const charT* pointer;
    typedef const charT* const_pointer;
    typedef const charT& reference;
    typedef const charT& const_reference;
    typedef implementation-defined const_iterator; // See [string.view.iterators]
    typedef const_iterator iterator;  // [Footnote: Because basic_string_view refers to a constant sequence, iterator and const_iterator are the same type. --end footnote]
    typedef reverse_iterator<const_iterator> const_reverse_iterator;
    typedef const_reverse_iterator reverse_iterator;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
    static constexpr size_type npos = size_type(-1);

    // [string.view.cons], construct/copy
    constexpr basic_string_view() noexcept;
    constexpr basic_string_view(const basic_string_view&) noexcept = default;
    basic_string_view& operator=(const basic_string_view&) noexcept = default;
    template<class Allocator>
    basic_string_view(const basic_string<charT, traits, Allocator>& str) noexcept;
    constexpr basic_string_view(const charT* str);

The above constructor is constexpr to take advantage of user-written traits classes that might provide a constexpr length() function. It can't be part of a constant expression with std::char_traits<char> without changes to that class.

    constexpr basic_string_view(const charT* str, size_type len);

No initializer_list constructor because C++11[dcl.init.list]p6 says it would likely store a dangling reference into the basic_string_view.

    // [string.view.iterators], iterators
    constexpr const_iterator begin() const noexcept;
    constexpr const_iterator end() const noexcept;
    constexpr const_iterator cbegin() const noexcept;
    constexpr const_iterator cend() const noexcept;

reverse_iterator methods aren’t constexpr because reverse_iterator isn’t a literal type. See LWG Issue 2208.

    const_reverse_iterator rbegin() const noexcept;
    const_reverse_iterator rend() const noexcept;
    const_reverse_iterator crbegin() const noexcept;
    const_reverse_iterator crend() const noexcept;

    // [string.view.capacity], capacity
    constexpr size_type size() const noexcept;
    constexpr size_type length() const noexcept;
    constexpr size_type max_size() const noexcept;
    constexpr bool empty() const noexcept;

    // [string.view.access], element access
    constexpr const charT& operator[](size_type pos) const;
    constexpr const charT& at(size_type pos) const;
    constexpr const charT& front() const;
    constexpr const charT& back() const;
    constexpr const charT* data() const noexcept;

    // [string.view.modifiers], modifiers:
    void clear() noexcept;
    void remove_prefix(size_type n);
    void remove_suffix(size_type n);
    void swap(basic_string_view& s) noexcept;

    // [string.view.ops], string operations:
    template<class Allocator>
    explicit operator basic_string<charT, traits, Allocator>() const;

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

    constexpr basic_string_view substr(size_type pos=0, size_type n=npos) const;
    constexpr int compare(basic_string_view 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 size_type find(basic_string_view 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 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 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 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 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 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;

   private:
    const charT* data_;  // exposition only
    size_type    size_;  // exposition only
  };
}  // namespace fundamentals_v1
}  // namespace experimental
}  // namespace std

In every specialization basic_string_view<charT, traits>, the type traits shall satisfy the character traits requirements (C++11[char.traits]), and the type traits::char_type shall name the same type as charT.

Add a subclause "x.1 basic_string_view constructors and assignment operators [string.view.cons]"

constexpr basic_string_view() noexcept;

Effects: Constructs an empty basic_string_view.

Postcondition: size_ == 0, and data_ has an unspecified, non-null value such that [data_,data_) is a valid range.

template<class Allocator>
basic_string_view(const basic_string<charT, traits, Allocator>& str) noexcept;

Effects: Constructs a basic_string_view, with the postconditions in Table [tab:string.view.ctr.1]

Remarks: The program shall not alter any of the values stored in the character array. [Footnote: This is the same requirement as on str.data() -- end footnote]

Table [tab:string.view.ctr.1] — basic_string_view(const basic_string&) effects
ElementValue
data_str.data()
size_str.size()
basic_string_view(const charT* str);

Requires: [str,str + traits::length(str)) is a valid range.

Effects: Constructs a basic_string_view referring to the same string as str, with the postconditions in Table [tab:string.view.ctr.2]

Table [tab:string.view.ctr.2] — basic_string_view(const charT*) effects
ElementValue
data_str
size_traits::length(str)

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

constexpr basic_string_view(const charT* str, size_type len);

Requires: str is not a null pointer and [str,str + len) is a valid range.

Effects: Constructs a basic_string_view, with the postconditions in Table [tab:string.view.ctr.3]

Table [tab:string.view.ctr.3] — basic_string_view(const charT*, size_type) effects
ElementValue
data_str
size_len

Add a subclause "x.2 basic_string_view iterator support [string.view.iterators]"

typedef implementation-defined const_iterator;

A constant random-access iterator type such that, for a const_iterator it, if &*(it+N) is valid, then it is equal to (&*it)+N.

For a basic_string_view str, any operation that invalidates a pointer in the range [str.data(), str.data()+str.size()) invalidates pointers, iterators, and references returned from str's methods.

All requirements on container iterators (C++11[container.requirements]) apply to basic_string_view::const_iterator as well.

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

Returns: An iterator such that &*begin() == data_ if *data_ is a valid expression and !empty(), or else an unspecified value such that [begin(),end()) is a valid range.

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

Returns: begin() + size()

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

Returns: An iterator which is semantically equivalent to reverse_iterator(end()).

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

Returns: An iterator which is semantically equivalent to reverse_iterator(begin()).

Add a subclause "x.3 basic_string_view capacity [string.view.capacity]"

constexpr size_type size() const noexcept;

Returns: size_

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_string_view.

constexpr bool empty() const noexcept;

Returns: size_ == 0.

Add a subclause "x.4 basic_string_view element access [string.view.access]"

constexpr const_reference operator[](size_type pos) const;

Requires: pos < size().

Returns: data_[pos]

Throws: Nothing.

[ Note: Unlike basic_string::operator[], basic_string_view::operator[](size()) has undefined behavior instead of returning charT(). — end note ]

constexpr const_reference at(size_type pos) const;

Throws: out_of_range if pos >= size().

Returns: operator[](pos).

constexpr const charT& front() const;

Requires: !empty()

Effects: Equivalent to return operator[](0).

constexpr const charT& back() const;

Requires: !empty()

Effects: Equivalent to return operator[](size() - 1).

constexpr const charT* data() const noexcept;

Returns: data_

[ Note: Unlike std::string::data() and string literals, data() may return a pointer to a buffer that is not null-terminated. Therefore it is typically a mistake to pass data() to a routine that takes just a const charT* and expects a null-terminated string. — end note ]

Add a subclause "x.5 basic_string_view modifiers [string.view.modifiers]"

void clear() noexcept;

Effects: Equivalent to *this = basic_string_view()

void remove_prefix(size_type n);

Requires: n <= size()

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

void remove_suffix(size_type n);

Requires: n <= size()

Effects: Equivalent to size_ -= n;

void swap(basic_string_view& s) noexcept

Effects: Exchanges the values of *this and s.

Add a subclause "x.6 basic_string_view string operations [string.view.ops]"

template<class Allocator>
explicit  // Footnote: This conversion is explicit to avoid accidental O(N) operations on type mismatches. --end footnote
operator basic_string<charT, traits, Allocator>() const;

Effects: Equivalent to return basic_string<charT, traits, Allocator>(str.begin(), str.end()).

[ Note: Users who want to control the allocator instance should call basic_string(str.begin(), str.end(), allocator) directly. -- end note ]

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

Throws: out_of_range if pos > size().

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

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

Effects: Equivalent to std::copy_n(begin() + pos, rlen, s).

Returns: rlen.

constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const;

Throws: out_of_range if pos > size().

Effects: Determines the effective length rlen of the string to reference as the smaller of n and size() - pos.

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

constexpr int compare(basic_string_view str) const noexcept;

Effects: Determines the effective length rlen of the strings to compare as the smallest of size() and str.size(). The function then compares the two strings by calling traits::compare(data(), str.data(), rlen).

Complexity: O(rlen)

Returns: The nonzero result if the result of the comparison is nonzero. Otherwise, returns a value as indicated in Table [tab:string.view.compare].

Table [tab:string.view.compare] — compare() results
ConditionReturn Value
size() < str.size()< 0
size() == str.size()0
size() > str.size()> 0
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(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(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(s, n2)).

Add a sub-subclause "x.6.1 Searching basic_string_view [string.view.find]"

Member functions in this section have complexity O(size() * argument.size()) at worst, although implementations are encouraged to do better.

Each member function of the form

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

is equivalent to return fx1(basic_string_view(s), pos).

Each member function of the form

constexpr return-type fx1(const charT* s, size_type pos, size_type n); // find() variants

is equivalent to return fx1(basic_string_view(s, n), pos).

Each member function of the form

constexpr return-type fx2(charT c, size_type pos); // find() variants

is equivalent to return fx2(basic_string_view(&c, 1), pos).

constexpr size_type find(basic_string_view str, size_type pos=0) const noexcept;

Effects: Determines the lowest position xpos, if possible, such that the following conditions obtain:

Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

Remarks: Uses traits::eq().

constexpr size_type rfind(basic_string_view str, size_type pos=npos) const noexcept;

Effects: Determines the highest position xpos, if possible, such that the following conditions obtain:

Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

Remarks: Uses traits::eq().

constexpr size_type find_first_of(basic_string_view str, size_type pos=0) const noexcept;

Effects: Determines the lowest position xpos, if possible, such that the following conditions obtain:

Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

Remarks: Uses traits::eq().

constexpr size_type find_last_of(basic_string_view str, size_type pos=npos) const noexcept;

Effects: Determines the highest position xpos, if possible, such that the following conditions obtain:

Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

Remarks: Uses traits::eq().

constexpr size_type find_first_not_of(basic_string_view str, size_type pos=0) const noexcept;

Effects: Determines the lowest position xpos, if possible, such that the following conditions obtain:

Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

Remarks: Uses traits::eq().

constexpr size_type find_last_not_of(basic_string_view str, size_type pos=npos) const noexcept;

Effects: Determines the highest position xpos, if possible, such that the following conditions obtain:

Returns: xpos if the function can determine such a value for xpos. Otherwise, returns npos.

Remarks: Uses traits::eq().

Add a subclause "x.7 basic_string_view non-member comparison functions [string.view.comparison]"

Implementations shall provide sufficient additional overloads so that an object t with an implicit conversion to basic_string_view<charT, traits> can be compared according to Table [tab:string.view.comparison.overloads], where sp is an instance of basic_string_view<charT, traits>.

Table [tab:string.view.comparison.overloads] — Additional basic_string_view comparison overloads
ExpressionEquivalent to
t == spbasic_string_view<charT, traits>(t) == sp
sp == tsp == basic_string_view<charT, traits>(t)
t != spbasic_string_view<charT, traits>(t) != sp
sp != tsp != basic_string_view<charT, traits>(t)
t < spbasic_string_view<charT, traits>(t) < sp
sp < tsp < basic_string_view<charT, traits>(t)
t > spbasic_string_view<charT, traits>(t) > sp
sp > tsp > basic_string_view<charT, traits>(t)
t <= spbasic_string_view<charT, traits>(t) <= sp
sp <= tsp <= basic_string_view<charT, traits>(t)
t >= spbasic_string_view<charT, traits>(t) >= sp
sp >= tsp >= basic_string_view<charT, traits>(t)

[ Example: A sample conforming implementation for operator== would be:

  template<class T> struct __identity { typedef T type; };
  template<class charT, class traits>
  constexpr bool operator==(
      basic_string_view<charT, traits> lhs,
      basic_string_view<charT, traits> rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }
  template<class charT, class traits>
  constexpr bool operator==(
      basic_string_view<charT, traits> lhs,
      typename __identity<basic_string_view<charT, traits>>::type rhs) noexcept {
    return lhs.compare(rhs) == 0;
  }
  template<class charT, class traits>
  constexpr bool operator==(
      typename __identity<basic_string_view<charT, traits>>::type lhs,
      basic_string_view<charT, traits> rhs) noexcept {
    return lhs.compare(rhs) == 0;
}

— end example ]

template<class charT, class traits>
  constexpr bool operator==(basic_string_view<charT,traits> lhs,
                            basic_string_view<charT,traits> rhs) noexcept;

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

template<class charT, class traits>
  constexpr bool operator!=(basic_string_view<charT,traits> lhs,
                            basic_string_view<charT,traits> rhs) noexcept;

Returns: !(lhs == rhs).

template<class charT, class traits>
  constexpr bool operator< (basic_string_view<charT,traits> lhs,
                            basic_string_view<charT,traits> rhs) noexcept;

Returns: lhs.compare(rhs) < 0.

template<class charT, class traits>
  constexpr bool operator> (basic_string_view<charT,traits> lhs,
                            basic_string_view<charT,traits> rhs) noexcept;

Returns: lhs.compare(rhs) > 0.

template<class charT, class traits>
  constexpr bool operator<=(basic_string_view<charT,traits> lhs,
                            basic_string_view<charT,traits> rhs) noexcept;

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

template<class charT, class traits>
  constexpr bool operator>=(basic_string_view<charT,traits> lhs,
                            basic_string_view<charT,traits> rhs) noexcept;

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

Add a subclause "x.8 Other basic_string_view non-member functions [string.view.nonmem]"

template<class charT, class traits = char_traits<charT>,
         class Allocator = allocator<charT> >
  basic_string<charT, traits, Allocator> to_string(
    basic_string_view<charT, traits> str,
    const Allocator& a = Allocator());

Complexity: O(str.size())

Returns: basic_string<charT, traits, Allocator>(str.begin(), str.end(), a).

Add a subclause "x.9 Inserters and extractors [string.view.io]

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

Effects: Behaves as a formatted output function (C++11[ostream.formatted.reqmts]) of os. Forms a character sequence seq, initially consisting of the elements defined by the range [str.begin(), str.end()). Determines padding for seq as described in C++11[ostream.formatted.reqmts]. Then inserts seq as if by calling os.rdbuf()->sputn(seq, n), where n is the larger of os.width() and str.size(); then calls os.width(0).

Returns: os

Add a subclause "x.10 Hash support [string.view.hash]"

template <> struct hash<experimental::string_view>;
template <> struct hash<experimental::u16string_view>;
template <> struct hash<experimental::u32string_view>;
template <> struct hash<experimental::wstring_view>;

Requires: the template specializations shall meet the requirements of class template hash (C++11[unord.hash]).

Alternate wording if data() may be nullptr

This section lists changes to the proposed wording if the LWG prefers to allow "null" string_views.

In subclause "x.1 basic_string_view constructors and assignment operators [string.view.cons]"

constexpr basic_string_view() noexcept;

Effects: Constructs an empty basic_string_view.

Postcondition: size_ == 0, and data_ == nullptr.data_ has an unspecified, non-null value such that [data_,data_) is a valid range.

Alternately, we could leave data_ unspecified-but-valid, but I worry about portability in that case.

constexpr basic_string_view(const charT* str, size_type len);

Requires: str is not a null pointer and [str,str + len) is a valid range.

Effects: Constructs a basic_string_view, with the postconditions in Table [tab:string.view.ctr.3]

Table [tab:string.view.ctr.3] — basic_string_view(const charT*, size_type) effects
ElementValue
data_str
size_len

In subclause "x.4 basic_string_view element access [string.view.access]"

constexpr const charT* data() const noexcept;

Returns: data_

[ Note: Unlike std::string::data() and string literals, data() may return a pointer to a buffer that is not null-terminated. Therefore it is typically a mistake to pass data() to a routine that takes just a const charT* and expects a null-terminated string. — end note ]

We could specify that if size_==0 then data() simply returns a valid pointer value, and not necessarily data_. This would let debugging implementations prevent developers from depending on a passed-through null value, but would prevent the std::split() proposal's Delimiters from using string_view to represent a 0-length position within a string at which to split.

Acknowledgements

I'd like to thank Marshall Clow, Olaf van der Spek, the Boost and std-proposals mailing lists, Chandler Carruth, Beman Dawes, Alisdair Meredith, and especially Daniel Krügler for help, advice, and wording in this paper.