Document number: N3180=10-0170
Date: 2010-11-11
J. Daniel Garcia
Project: Programming Language C++, Library Working Group
Reply To: josedaniel.garcia@uc3m.es

N3180 - More on noexcept for the Strings Library

This paper studies possible changes to the Strings Library to make broad use of noexcept. The paper addresses National Body comments CH 16 and GB 60.

Changes in this paper are restricted to chapter 21 (strings library).

All changes in this paper are relative to N3126

Discussion

In general, this paper takes a aggresive path for noexceptifying the string library. The basic assumption is that any char traits must offer noexcept operations. This basic assumption may be arguable but I am not aware of any string implementation where operations defined in char traits may throw.

Char traits

Operations at character traits have been specified as non-throwing. Those operations have been made noexcept in every library provided instantiation.

String classes

In general, many operations of string classes may throw. One exception are member functions erase() and pop_back which cannot throw (21.4.1/3). However, 21.4.6.5/2 specifies ther first versionerase() to throw.

Move constructor has been made throwing as it can only throw if moving an allocator may throw, but allocators do not throw on move.

Iterator support member functions return iterators to the beginning or the end of the string. The only situation in which those members could throw is returning the iterator. However, those implementation-defined iterator types are cross-referenced to clause 23.2 and 23.2.1/11 states that no copy constructor or assignment operator of a returned iterator throws an exception. Thus, those member functions have been made noexcept.

Member function length() is specified in terms of the non-throwing size(). Thus, it has been made noexcept. Besides, max_size has also been made noexcept, as this value can be computed without the need of any throwing operation. This was also the case of capacity(). However, member function shrink_to_fit() cannot be made noexcept as it may need to reallocate some memory which is potentially throwing.

Member function clear() behaves as a call to erase(begin(), end()) that cannot throw by definition of erase().

Member function empty() cannot throw because is specified in terms of non-throwing size().

Member functions front() and back() cannot throw. They are specified in terms of non-throwing operator [] accessing to a knowing position.

Member function pop_back() is specified in terms of erase() on an existing position and length. That makes impossible to throw, independently of wether erase() is non-throwing or not (see open points).

Member function swap() is made noexcept by paper N3195. However, it is made noexcept here to guarantee consistency.

Member function get_allocator may be made noexcept because any allocator satisfying allocator requirements cannot throw when copying and/or moving.

Several member functions for finding follow a common pattern. The basic version is based on traits::eq() (and is consquently noexcept) and the rest of versions are based on the basic version. This has been found for member functions find(), rfind(), find_first_of(), find_last_of, find_first_not_of and find_last_not_of. Thus, all of them have been made noexcept.

Member function compare() has several versions. Comparison between to basic_string's can be made non-throwing as it only coudl throw if the corresponding traits::compare() threw, which is not possible. Besides, the version taking a charT* as an argument (21.4.7.9/5) could be made conditionally noexcept with a small change. Currently its effects are specified as a call to compare(basic_string(s)). Such a call is potentially throwing if allocation is performed. I propose that the effects are specified in terms of as-if to allow a noexcept specification.

Non-member comparsion operators may be expressed in terms of member function compare(). This has been very easily done for == and !=. However, for the rest of comparison operators some Returns: clauses have been re-worked to make clear that they can be expressed without the need of temporals that could cause allocation exceptions.

Non-member function swap() has been made noexcept as it is based on non-throwing member swap().

Acknowledgments

Daniel Krügler reviewed a preliminary version of this paper providing very useful suggestions and hints.

Proposed Wording

21.2.3.1 struct char_traits<char> [char.traits.specializations.char]

Before p. 1
namespace std {
  template<> struct char_traits<char> {
    typedef char char_type;
    typedef int int_type;
    typedef streamoff off_type;
    typedef streampos pos_type;
    typedef mbstate_t state_type;

    static void assign(char_type& c1, const char_type& c2) noexcept;
    static constexpr bool eq(char_type c1, char_type c2) noexcept;
    static constexpr bool lt(char_type c1, char_type c2) noexcept;

    static int compare(const char_type* s1, const char_type* s2, size_t n) noexcept;
    static size_t length(const char_type* s) noexcept;
    static const char_type* find(const char_type* s, size_t n,
                                 const char_type& a) noexcept;
    static char_type* move(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* copy(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* assign(char_type* s, size_t n, char_type a) noexcept;

    static constexpr int_type not_eof(int_type c) noexcept;
    static constexpr char_type to_char_type(int_type c) noexcept;
    static constexpr int_type to_int_type(char_type c) noexcept;
    static constexpr bool eq_int_type(int_type c1, int_type c2) noexcept;
    static constexpr int_type eof() noexcept;
  };
}

21.2.3.2 struct char_traits<char16_t> [char.traits.specializations.char16_t]

Before p. 1
namespace std {
  template<> struct char_traits<char16_t> {
    typedef char16_t char_type;
    typedef uint_least16_t int_type;
    typedef streamoff off_type;
    typedef u16streampos pos_type;
    typedef mbstate_t state_type;

    static void assign(char_type& c1, const char_type& c2) noexcept;
    static constexpr bool eq(char_type c1, char_type c2) noexcept;
    static constexpr bool lt(char_type c1, char_type c2) noexcept;

    static int compare(const char_type* s1, const char_type* s2, size_t n) noexcept;
    static size_t length(const char_type* s) noexcept;
    static const char_type* find(const char_type* s, size_t n,
                                 const char_type& a) noexcept;
    static char_type* move(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* copy(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* assign(char_type* s, size_t n, char_type a) noexcept;
    
    static constexpr int_type not_eof(int_type c) noexcept;
    static constexpr char_type to_char_type(int_type c) noexcept;
    static constexpr int_type to_int_type(char_type c) noexcept;
    static constexpr bool eq_int_type(int_type c1, int_type c2) noexcept;
    static constexpr int_type eof() noexcept;
  };
}

21.2.3.3 struct char_traits<char32_t> [char.traits.specializations.char32_t]

Before p. 1
namespace std {
  template<> struct char_traits<char32_t> {
    typedef char32_t char_type;
    typedef uint_least32_t int_type;
    typedef streamoff off_type;
    typedef u32streampos pos_type;
    typedef mbstate_t state_type;

    static void assign(char_type& c1, const char_type& c2) noexcept;
    static constexpr bool eq(char_type c1, char_type c2) noexcept;
    static constexpr bool lt(char_type c1, char_type c2) noexcept;
    static int compare(const char_type* s1, const char_type* s2, size_t n) noexcept;
    static size_t length(const char_type* s) noexcept;
    static const char_type* find(const char_type* s, size_t n,
                                 const char_type& a) noexcept;
    static char_type* move(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* copy(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* assign(char_type* s, size_t n, char_type a) noexcept;

    static constexpr int_type not_eof(int_type c) noexcept;
    static constexpr char_type to_char_type(int_type c) noexcept;
    static constexpr int_type to_int_type(char_type c) noexcept;
    static constexpr bool eq_int_type(int_type c1, int_type c2) noexcept;
    static constexpr int_type eof() noexcept;
  };
}

21.2.3.4 struct char_traits [char.traits.specializations.wchar.t]

Before p. 1
namespace std {
  template<> struct char_traits<wchar_t> {
    typedef wchar_t char_type;
    typedef wint_t int_type;
    typedef streamoff off_type;
    typedef wstreampos pos_type;
    typedef mbstate_t state_type;

    static void assign(char_type& c1, const char_type& c2) noexcept;
    static constexpr bool eq(char_type c1, char_type c2) noexcept;
    static constexpr bool lt(char_type c1, char_type c2) noexcept;

    static int compare(const char_type* s1, const char_type* s2, size_t n) noexcept;
    static size_t length(const char_type* s) noexcept;
    static const char_type* find(const char_type* s, size_t n,
                                 const char_type& a) noexcept;
    static char_type* move(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* copy(char_type* s1, const char_type* s2, size_t n) noexcept;
    static char_type* assign(char_type* s, size_t n, char_type a) noexcept;
    
    static constexpr int_type not_eof(int_type c) noexcept;
    static constexpr char_type to_char_type(int_type c) noexcept;
    static constexpr int_type to_int_type(char_type c) noexcept;
    static constexpr bool eq_int_type(int_type c1, int_type c2) noexcept;
    static constexpr int_type eof() noexcept;
  };
}

21.3 String classes [string.classes]

After p. 1
namespace std {
...
  template<class charT, class traits, class Allocator>
    bool operator==(const basic_string<charT,traits,Allocator>& lhs,
                    const basic_string<charT,traits,Allocator>& rhs) noexcep;
  template<class charT, class traits, class Allocator>
    bool operator==(const charT* lhs,
		    const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator==(const basic_string<charT,traits,Allocator>& lhs,
		    const charT* rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator!=(const basic_string<charT,traits,Allocator>& lhs,
		    const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator!=(const charT* lhs,
		    const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator!=(const basic_string<charT,traits,Allocator>& lhs,
                    const charT* rhs) noexcept;

  template<class charT, class traits, class Allocator>
    bool operator< (const basic_string<charT,traits,Allocator>& lhs,
		       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator< (const basic_string<charT,traits,Allocator>& lhs,
		       const charT* rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator< (const charT* lhs,
		       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator> (const basic_string<charT,traits,Allocator>& lhs,
		       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator> (const basic_string<charT,traits,Allocator>& lhs,
		       const charT* rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator> (const charT* lhs,
                       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator<=(const basic_string<charT,traits,Allocator>& lhs,
		       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator<=(const basic_string<charT,traits,Allocator>& lhs,
		       const charT* rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator<=(const charT* lhs,
		       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator>=(const basic_string<charT,traits,Allocator>& lhs,
		       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator>=(const basic_string<charT,traits,Allocator>& lhs,
		       const charT* rhs) noexcept;
  template<class charT, class traits, class Allocator>
    bool operator>=(const charT* lhs,
                       const basic_string<charT,traits,Allocator>& rhs) noexcept;
  // 21.4.8.8: swap
  template<class charT, class traits, class Allocator>
    void swap(basic_string<charT,traits,Allocator>& lhs,
	      basic_string<charT,traits,Allocator>& rhs) noexcept;

...
}

21.4 Class template basic_string [basic.string]

After p. 5
namespace std {
  template<class charT, class traits = char_traits<charT>,
              class Allocator = allocator<charT> >
  class basic_string {
  public:
...
    // 21.4.2 construct/copy/destroy:
    basic_string(basic_string&& str) noexcept;
...
    // 21.4.3 iterators:
    iterator begin() noexcept;
    const_iterator begin() const noexcept;
    iterator end() noexcept;

    const_iterator end() const noexcept;
    reverse_iterator rbegin() noexcept;
    const_reverse_iterator rbegin() const noexcept;

    reverse_iterator rend() noexcept;
    const_reverse_iterator rend() const noexcept;
    const_iterator cbegin() const noexcept;

    const_iterator cend() const noexcept;
    const_reverse_iterator crbegin() const noexcept;
    const_reverse_iterator crend() const noexcept;

    // 21.4.4 capacity:
...
    size_type length() const noexcept;
    size_type max_size() const noexcept;
...
    size_type capacity() const noexcept;
...
    void clear() noexcept;
    bool empty() const noexcept;

    // 21.4.5 element access:
...
    const charT& front() const noexcept;
    charT& front() noexcept;
    const charT& back() const noexcept;
    charT& back() noexcept;

    // 21.4.6 modifiers:
...
    void pop_back() noexcept;
...
    void swap(basic_string& str) noexcept;

    // 21.4.7 string operations:
...
    allocator_type get_allocator() 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 noexcept;
    size_type find (charT c, size_type pos = 0) 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 noexcept;
    size_type rfind(charT c, size_type pos = npos) 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 noexcept;
    size_type find_first_of(charT c, size_type pos = 0) 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 noexcept;
    size_type find_last_of (charT c, size_type pos = npos) 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 noexcept;
    size_type find_first_not_of(charT c, size_type pos = 0) 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 noexcept;
    size_type find_last_not_of (charT c, size_type pos = npos) const noexcept;
...
    int compare(const basic_string& str) const noexcept; 
...
    int compare(const charT* s) const noexcept;
  };
}

21.4.1 basic_string general requirements [string.require]

Modify p. 3

No erase() or pop_back() member function shall throw any exceptions.

21.4.2 basic_string constructors and assigment operators [string.cons]

After p. 1
basic_string(const basic_string& str);
basic_string(basic_string&& str)noexcept;

21.4.3 basic_string iterator support [string.iterators]

Before p. 1
iterator begin() noexcept;
const_iterator begin() const noexcept;
const_iterator cbegin() const noexcept;
Before p. 2
iterator end() noexcept;
const_iterator end() const noexcept;
const_iterator cend() const noexcept;
Before p. 3
reverse_iterator rbegin() noexcept;
const_reverse_iterator rbegin() const noexcept;
const_reverse_iterator crbegin() const noexcept;
Before p. 4
reverse_iterator rend() noexcept;
const_reverse_iterator rend() const noexcept;
const_reverse_iterator crend() const noexcept;

21.4.4 basic_string capacity [string.capacity]

After p. 3
size_type length() const noexcept;
After p. 4
size_type max_size() const noexcept;
After p. 10
size_type capacity() const noexcept;
After p. 15
void clear() noexcept;
After p. 16
bool empty() const noexcept;

21.4.5 basic_string element access [string.access]

After p. 7
const charT& front() const noexcept;
charT& front() noexcept;
After p. 9
const charT& back() const noexcept;
charT& back() noexcept;

21.4.6.5 basic_string::erase [string::erase]

After p. 10
void pop_back() noexcept;

21.4.6.8 basic_string::swap [string::swap]

Before p. 1
void swap(basic_string& s) noexcept;

1 Throws: Nothing.

21.4.7.1 basic_string accessors [string.accessors]

After p. 4
allocator_type get_allocator() const noexcept;

21.4.7.2 basic_string::find [string::find]

Before p. 1
size_type find(const basic_string& str,
               size_type pos = 0) const noexcept;
After p. 3
size_type find(const charT* s, size_type pos, size_type n) const noexcept;
After p. 4
size_type find(const charT* s, size_type pos = 0) const noexcept;
After p. 6
size_type find(charT c, size_type pos = 0) const noexcept;

21.4.7.3 basic_string::rfind [string::rfind]

Before p. 1
size_type rfind(const basic_string& str,
               size_type pos = 0) const noexcept;
After p. 3
size_type rfind(const charT* s, size_type pos, size_type n) const noexcept;
After p. 4
size_type rfind(const charT* s, size_type pos = 0) const noexcept;
After p. 6
size_type rfind(charT c, size_type pos = 0) const noexcept;

21.4.7.4 basic_string::find_first_of [string::find.first.of]

Before p. 1
size_type
  find_first_of(const basic_string& str,
		size_type pos = 0) const noexcept;
After p. 3
size_type
  find_first_of(const charT* s, size_type pos, size_type n) const noexcept;
After p. 4
size_type find_first_of(const charT* s, size_type pos = 0) const noexcept;
After p. 6
size_type find_first_of(charT c, size_type pos = 0) const noexcept;

21.4.7.5 basic_string::find_last_of [string::find.last.of]

Before p. 1
size_type
  find_last_of(const basic_string& str,
		size_type pos = 0) const noexcept;
After p. 3
size_type
  find_last_of(const charT* s, size_type pos, size_type n) const noexcept;
After p. 4
size_type find_last_of(const charT* s, size_type pos = 0) const noexcept;
After p. 6
size_type find_last_of(charT c, size_type pos = 0) const noexcept;

21.4.7.6 basic_string::find_first_not_of [string::find.first.not.of]

Before p. 1
size_type
  find_first_not_of(const basic_string& str,
		    size_type pos = 0) const noexcept;
After p. 3
size_type
  find_first_not_of(const charT* s, size_type pos, size_type n) const noexcept;
After p. 4
size_type find_first_not_of(const charT* s, size_type pos = 0) const noexcept;
After p. 6
size_type find_first_not_of(charT c, size_type pos = 0) const noexcept;

21.4.7.7 basic_string::find_last_not_of [string::find.last.not.of]

Before p. 1
size_type
  find_last_not_of(const basic_string& str,
		size_type pos = 0) const noexcept;
After p. 3
size_type
  find_last_not_of(const charT* s, size_type pos, size_type n) const noexcept;
After p. 4
size_type find_last_not_of(const charT* s, size_type pos = 0) const noexcept;
After p. 6
size_type find_last_not_of(charT c, size_type pos = 0) const noexcept;

21.4.7.9 basic_string::compare [string::compare]

Before p. 1
int compare(const basic_string& str) const noexcept;
After p. 4
int compare(const charT *s) const noexcept;

5 Returns: Equivalent to compare(basic_string(s)).

21.4.8.2 operator== [string::operator==]

Before p. 1
template<class charT, class traits, class Allocator>
  bool operator==(const basic_string<charT,traits,Allocator>& lhs,
		  const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 1
template<class charT, class traits, class Allocator>
  bool operator==(const charT* lhs,
                  const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 2
template<class charT, class traits, class Allocator>
  bool operator==(const basic_string<charT,traits,Allocator>& lhs,
                  const charT* rhs) noexcept;

21.4.8.3 operator!= [string::op!=]

Before p. 1
template<class charT, class traits, class Allocator>
  bool operator!=(const basic_string<charT,traits,Allocator>& lhs,
                  const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 1
template<class charT, class traits, class Allocator>
  bool operator!=(const charT* lhs,
		  const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 2
template<class charT, class traits, class Allocator>
  bool operator!=(const basic_string<charT,traits,Allocator>& lhs,
                  const charT* rhs) noexcept;

21.4.8.4 operator< [string::op<]

Before p. 1
template<class charT, class traits, class Allocator>
  bool operator< (const basic_string<charT,traits,Allocator>& lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 1
template<class charT, class traits, class Allocator>
  bool operator< (const charT* lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;

2 Returns: basic_string<charT,traits,Allocator>(lhs) < rhs rhs.compare(lhs) > 0.

After p. 2
template<class charT, class traits, class Allocator>
  bool operator< (const basic_string<charT,traits,Allocator>& lhs,
                     const charT* rhs) noexcept;

3 Returns: lhs < basic_string<charT,traits,Allocator>(rhs) lhs.compare(rhs) < 0.

21.4.8.5 operator> [string::op>]

Before p. 1
template<class charT, class traits, class Allocator>
  bool operator> (const basic_string<charT,traits,Allocator>& lhs,
		     const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 1
template<class charT, class traits, class Allocator>
  bool operator> (const charT* lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;

2 Returns: basic_string<charT,traits,Allocator>(lhs) > rhs rhs.compare(lhs) < 0.

After p. 2
template<class charT, class traits, class Allocator>
  bool operator> (const basic_string<charT,traits,Allocator>& lhs,
		     const charT* rhs) noexcept;

3 Returns: lhs > basic_string<charT,traits,Allocator>(rhs) lhs.compare(rhs) > 0.

21.4.8.6 operator<= [string::op<=]

Before p. 1
template<class charT, class traits, class Allocator>
  bool operator<=(const basic_string<charT,traits,Allocator>& lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 1
template<class charT, class traits, class Allocator>
  bool operator<=(const charT* lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;

2 Returns: basic_string<charT,traits,Allocator>(lhs) <= rhs rhs.compare(lhs) >= 0.

After p. 2
template<class charT, class traits, class Allocator>
  bool operator<=(const basic_string<charT,traits,Allocator>& lhs,
                     const charT* rhs) noexcept;

3 Returns: lhs <= basic_string<charT,traits,Allocator>(rhs) lhs.compare(rhs) <= 0.

21.4.8.7 operator>= [string::op>=]

Before p. 1
template<class charT, class traits, class Allocator>
  bool operator>=(const basic_string<charT,traits,Allocator>& lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;
After p. 1
template<class charT, class traits, class Allocator>
  bool operator>=(const charT* lhs,
                     const basic_string<charT,traits,Allocator>& rhs) noexcept;

2 Returns: basic_string<charT,traits,Allocator>(lhs) >= rhs rhs.compare(lhs) <= 0.

After p. 2
template<class charT, class traits, class Allocator>
  bool operator>=(const basic_string<charT,traits,Allocator>& lhs,
                     const charT* rhs) noexcept;

3 Returns: lhs >= basic_string<charT,traits,Allocator>(rhs) lhs.compare(rhs) >= 0.

21.4.8.8 swap [string.special]

Before p. 1
template<class charT, class traits, class Allocator>
  void swap(basic_string<charT,traits,Allocator>& lhs,
            basic_string<charT,traits,Allocator>& rhs) noexcept;