2064. More noexcept issues in basic_string

Section: 24.3.2 [basic.string] Status: C++14 Submitter: Howard Hinnant Opened: 2011-05-29 Last modified: 2016-11-12

Priority: Not Prioritized

View other active issues in [basic.string].

View all other issues in [basic.string].

View all issues with C++14 status.

Discussion:

The following inconsistencies regarding noexcept for basic_string are noted.

Member swap is not marked noexcept:

void swap(basic_string& str);

But the global swap is marked noexcept:

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

But only in the definition, not in the synopsis.

All comparison operators are marked noexcept in their definitions, but not in the synopsis.

The compare function that takes a pointer:

int compare(const charT *s) const;

is not marked noexcept. But some of the comparison functions which are marked noexcept (only in their definition) are specified to call the throwing compare operator:

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

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

All functions with a narrow contract should not be declared as noexcept according to the guidelines presented in n3279. Among these narrow contract functions are the swap functions (26.2.1 [container.requirements.general] p. 8) and functions with non-NULL const charT* parameters.

[2011-06-08 Daniel provides wording]

[Bloomington, 2011]

Move to Ready

Proposed resolution:

This wording is relative to the FDIS. Both move-assignment operator and the moving assign function are not touched by this issue, because they are handled separately by issue 2063.

  1. Modify the header <string> synopsis in 24.3 [string.classes] as indicated (Rationale: Adding noexcept to these specific overloads is in sync with applying the same rule to specific overloads of the member functions find, compare, etc. This approach deviates from that taken in n3279, but seems more consistent given similar application for comparable member functions):

    #include <initializer_list>
    
    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) 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 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 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;
      […]
    }
    
  2. Modify the class template basic_string synopsis in 24.3.2 [basic.string] as indicated (Remark 1: The noexcept at the move-constructor is fine, because even for a small-object optimization there is no problem here, because basic_string::value_type is required to be a non-array POD as of 24.1 [strings.general] p1, Remark 2: This proposal removes the noexcept at single character overloads of find, rfind, etc. because they are defined in terms of potentially allocating functions. It seems like an additional issue to me to change the semantics in terms of non-allocating functions and adding noexcept instead):

    namespace std {
      template<class charT, class traits = char_traits<charT>,
        class Allocator = allocator<charT> >
      class basic_string {
      public:
        […]
        // [string.ops], string operations:
        […]
        size_type find (charT c, size_type pos = 0) const noexcept;
        […]
        size_type rfind(charT c, size_type pos = npos) const noexcept;
        […]
        size_type find_first_of(charT c, size_type pos = 0) const noexcept;
        […]
        size_type find_last_of (charT c, size_type pos = npos) const noexcept;
        […]
        size_type find_first_not_of(charT c, size_type pos = 0) const noexcept;
        […]
        size_type find_last_not_of (charT c, size_type pos = npos) const noexcept;
        […]
      };
    }
    
  3. Modify 24.3.2.7.2 [string.find] before p5 and before p7 as indicated:

    size_type find(const charT* s, size_type pos = 0) const noexcept;
    […]
    size_type find(charT c, size_type pos = 0) const noexcept;
    

    -7- Returns: find(basic_string<charT,traits,Allocator>(1,c), pos).

  4. Modify 24.3.2.7.3 [string.rfind] before p7 as indicated:

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

    -7- Returns: rfind(basic_string<charT,traits,Allocator>(1,c),pos).

  5. Modify 24.3.2.7.4 [string.find.first.of] before p7 as indicated:

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

    -7- Returns: find_first_of(basic_string<charT,traits,Allocator>(1,c), pos).

  6. Modify 24.3.2.7.5 [string.find.last.of] before p7 as indicated:

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

    -7- Returns: find_last_of(basic_string<charT,traits,Allocator>(1,c),pos).

  7. Modify 24.3.2.7.6 [string.find.first.not.of] before p7 as indicated:

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

    -7- Returns: find_first_not_of(basic_string(1, c), pos).

  8. Modify 24.3.2.7.7 [string.find.last.not.of] before p7 as indicated:

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

    -7- Returns: find_last_not_of(basic_string(1, c), pos).

  9. Modify 24.3.3.2 [string.operator==] before p2+p3 as indicated:

    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;
    
  10. Modify 24.3.3.3 [string.op!=] before p2+p3 as indicated:

    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;
    
  11. Modify 24.3.3.4 [string.op<] before p2+p3 as indicated:

    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;
    
  12. Modify 24.3.3.5 [string.op>] before p2+p3 as indicated:

    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;
    
  13. Modify 24.3.3.6 [string.op<=] before p2+p3 as indicated:

    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;
    
  14. Modify 24.3.3.7 [string.op>=] before p2+p3 as indicated:

    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;
    
  15. Modify 24.3.3.8 [string.special] as indicated (Remark: The change of the semantics guarantees as of 20.4.1.4 [structure.specifications] p4 that the "Throws: Nothing" element of member swap is implied):

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

    -1- Effects: Equivalent to lhs.swap(rhs);