2836. More string operations should be noexcept

Section: 27.4.3 [basic.string], 27.4.3.8.2 [string.find], 27.4.3.8.4 [string.compare] Status: Resolved Submitter: Jonathan Wakely Opened: 2016-12-05 Last modified: 2023-02-07

Priority: 2

View other active issues in [basic.string].

View all other issues in [basic.string].

View all issues with Resolved status.

Discussion:

Currently some overloads of basic_string::find are noexcept and some are not. Historically this was because some were specified in terms of constructing a temporary basic_string, which could throw. In practice creating a temporary (and potentially allocating memory) is a silly implementation, and so they could be noexcept. In the C++17 draft most of them have been changed to create a temporary basic_string_view instead, which can't throw anyway (P0254R2 made those changes).

This is confusing for users, as they need to carefully check which overload their code will resolve to, and consider whether that overload can throw. Refactoring code can change whether it calls a throwing or non-throwing overload. This is an unnecessary burden on users when realistically none of the functions will ever throw.

The find, rfind, find_first_of, find_last_of, find_first_not_of and find_last_not_of overloads that are defined in terms of basic_string_view should be noexcept, or "Throws: Nothing." for the ones with narrow contracts (even though those narrow contracts are not enforcable or testable).

The remaining overloads that are still specified in terms of a temporary string could also be noexcept. They construct basic_string of length one (which won't throw for an SSO implementation anyway), but can easily be defined in terms of basic_string_view instead.

There's one basic_string::compare overload that is still defined in terms of a temporary basic_string, which should be basic_string_view and so can also be noexcept (the other compare overloads can throw out_of_range).

[2016-12-15, Tim Song comments]

The following overloads invoking basic_string_view<charT>(s, n) are implicitly narrow-contract (the basic_string_view constructor requires [s, s+n) to be a valid range) and should be "Throws: Nothing" rather than noexcept:

size_type find(const charT* s, size_type pos, size_type n) const;
size_type rfind(const charT* s, size_type pos, size_type n) const;
size_type find_first_of(const charT* s, size_type pos, size_type n) const;
size_type find_last_of(const charT* s, size_type pos, size_type n) const;
size_type find_first_not_of(const charT* s, size_type pos, size_type n) const;
size_type find_last_not_of(const charT* s, size_type pos, size_type n) const;

Similarly, the basic_string_view constructor invoked by this overload of compare requires [s, s + traits::length(s)) to be a valid range, and so it should be "Throws: Nothing" rather than noexcept:

int compare(const charT* s) const; 

[2017-01-27 Telecon]

Priority 2; Jonathan to provide updated wording

[2017-10-22, Marshall comments]

libc++ already has all of these member fns marked as noexcept

[2017-11 Albuquerque Saturday issues processing]

All the fns that take a const char * are narrow contract, and so can't be noexcept. Should be "Throws: Nothing" instead. Alisdair to re-word.

[2018-08 mailing list discussion]

This will be resolved by Tim's string rework paper.

Resolved by the adoption of P1148 in San Diego.

Proposed resolution:

This wording is relative to N4618.

  1. Change class template synopsis std::basic_string, 27.4.3 [basic.string], as indicated:

    size_type find (basic_string_view<charT, traits> sv,
                    size_type pos = 0) const noexcept;
    size_type find (const basic_string& str, size_type pos = 0) const noexcept;
    size_type find (const charT* s, size_type pos, size_type n) const noexcept;
    size_type find (const charT* s, size_type pos = 0) const;
    size_type find (charT c, size_type pos = 0) const noexcept;
    size_type rfind(basic_string_view<charT, traits> sv,
                    size_type pos = npos) const noexcept;
    size_type rfind(const basic_string& str, size_type pos = npos) const noexcept;
    size_type rfind(const charT* s, size_type pos, size_type n) const noexcept;
    size_type rfind(const charT* s, size_type pos = npos) const;
    size_type rfind(charT c, size_type pos = npos) const noexcept;
    size_type find_first_of(basic_string_view<charT, traits> sv,
                            size_type pos = 0) const noexcept;
    size_type find_first_of(const basic_string& str,
                            size_type pos = 0) const noexcept;
    size_type find_first_of(const charT* s,
                            size_type pos, size_type n) const noexcept;
    size_type find_first_of(const charT* s, size_type pos = 0) const;
    size_type find_first_of(charT c, size_type pos = 0) const noexcept;
    size_type find_last_of (basic_string_view<charT, traits> sv,
                            size_type pos = npos) const noexcept;
    size_type find_last_of (const basic_string& str,
                            size_type pos = npos) const noexcept;
    size_type find_last_of (const charT* s,
                            size_type pos, size_type n) const noexcept;
    size_type find_last_of (const charT* s, size_type pos = npos) const;
    size_type find_last_of (charT c, size_type pos = npos) const noexcept;
    size_type find_first_not_of(basic_string_view<charT, traits> sv,
                                size_type pos = 0) const noexcept;
    size_type find_first_not_of(const basic_string& str,
                                size_type pos = 0) const noexcept;
    size_type find_first_not_of(const charT* s, size_type pos,
                                size_type n) const noexcept;
    size_type find_first_not_of(const charT* s, size_type pos = 0) const;
    size_type find_first_not_of(charT c, size_type pos = 0) const noexcept;
    size_type find_last_not_of (basic_string_view<charT, traits> sv,
                                size_type pos = npos) const noexcept;
    size_type find_last_not_of (const basic_string& str,
                                size_type pos = npos) const noexcept;
    size_type find_last_not_of (const charT* s, size_type pos,
                                size_type n) const noexcept;
    size_type find_last_not_of (const charT* s,
                                size_type pos = npos) const;
    size_type find_last_not_of (charT c, size_type pos = npos) const noexcept;
    
    basic_string substr(size_type pos = 0, size_type n = npos) const;
    int compare(basic_string_view<charT, traits> sv) const noexcept;
    int compare(size_type pos1, size_type n1,
                basic_string_view<charT, traits> sv) const;
    template<class T>
      int compare(size_type pos1, size_type n1, const T& t,
                  size_type pos2, size_type n2 = npos) const;
    int compare(const basic_string& str) const noexcept;
    int compare(size_type pos1, size_type n1,
                const basic_string& str) const;
    int compare(size_type pos1, size_type n1,
                const basic_string& str,
                size_type pos2, size_type n2 = npos) const;
    int compare(const charT* s) const noexcept;
    int compare(size_type pos1, size_type n1,
                const charT* s) const;
    int compare(size_type pos1, size_type n1,
                const charT* s, size_type n2) const;
    
  2. Change 27.4.3.8.2 [string.find] as indicated:

    size_type find(const charT* s, size_type pos, size_type n) const noexcept;
    

    -5- Returns: find(basic_string_view<charT, traits>(s, n), pos).

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

    -6- Requires: s points to an array of at least traits::length(s) + 1 elements of charT.

    -7- Returns: find(basic_string_view<charT, traits>(s), pos).

    -?- Throws: Nothing.

    size_type find(charT c, size_type pos = 0) const noexcept;
    

    -8- Returns: find(basic_string_view<charT, traits>(addressof(c), 1)(1, c), pos).

  3. Change [string.rfind] as indicated:

    size_type rfind(const charT* s, size_type pos, size_type n) const noexcept;
    

    -5- Returns: rfind(basic_string_view<charT, traits>(s, n), pos).

    size_type rfind(const charT* s, size_type pos = npos) const;
    

    -6- Requires: s points to an array of at least traits::length(s) + 1 elements of charT.

    -7- Returns: rfind(basic_string_view<charT, traits>(s), pos).

    -?- Throws: Nothing.

    size_type rfind(charT c, size_type pos = npos) const noexcept;
    

    -8- Returns: rfind(basic_string_view<charT, traits>(addressof(c), 1)(1, c), pos).

  4. Change [string.find.first.of] as indicated:

    size_type
      find_first_of(const charT* s, size_type pos, size_type n) const noexcept;
    

    -5- Returns: find_first_of(basic_string_view<charT, traits>(s, n), pos).

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

    -6- Requires: s points to an array of at least traits::length(s) + 1 elements of charT.

    -7- Returns: find_first_of(basic_string_view<charT, traits>(s), pos).

    -?- Throws: Nothing.

    size_type find_first_of(charT c, size_type pos = 0) const noexcept;
    

    -8- Returns: find_first_of(basic_string_view<charT, traits>(addressof(c), 1)(1, c), pos).

  5. Change [string.find.last.of] as indicated:

    size_type find_last_of(const charT* s, size_type pos, size_type n) const noexcept;
    

    -5- Returns: find_last_of(basic_string_view<charT, traits>(s, n), pos).

    size_type find_last_of(const charT* s, size_type pos = npos) const;
    

    -6- Requires: s points to an array of at least traits::length(s) + 1 elements of charT.

    -7- Returns: find_last_of(basic_string_view<charT, traits>(s), pos).

    -?- Throws: Nothing.

    size_type find_last_of(charT c, size_type pos = npos) const noexcept;
    

    -8- Returns: find_last_of(basic_string_view<charT, traits>(addressof(c), 1)(1, c), pos).

  6. Change [string.find.first.not.of] as indicated:

    size_type
      find_first_not_of(const charT* s, size_type pos, size_type n) const noexcept;
    

    -5- Returns: find_first_not_of(basic_string_view<charT, traits>(s, n), pos).

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

    -6- Requires: s points to an array of at least traits::length(s) + 1 elements of charT.

    -7- Returns: find_first_not_of(basic_string_view<charT, traits>(s), pos).

    -?- Throws: Nothing.

    size_type find_first_not_of(charT c, size_type pos = 0) const noexcept;
    

    -8- Returns: find_first_not_of(basic_string_view<charT, traits>(addressof(c), 1)(1, c), pos).

  7. Change [string.find.last.not.of] as indicated:

    size_type find_last_not_of(const charT* s, size_type pos,
                               size_type n) const noexcept;
    

    -5- Returns: find_last_not_of(basic_string_view<charT, traits>(s, n), pos).

    size_type find_last_not_of(const charT* s, size_type pos = npos) const;
    

    -6- Requires: s points to an array of at least traits::length(s) + 1 elements of charT.

    -7- Returns: find_last_not_of(basic_string_view<charT, traits>(s), pos).

    -?- Throws: Nothing.

    size_type find_last_not_of(charT c, size_type pos = npos) const noexcept;
    

    -8- Returns: find_last_not_of(basic_string_view<charT, traits>(addressof(c), 1)(1, c), pos).

  8. Change 27.4.3.8.4 [string.compare] as indicated:

    int compare(const charT* s) const noexcept;
    

    -9- Returns: compare(basic_string_view<charT, traits>(s)).