3662. basic_string::append/assign(NTBS, pos, n) suboptimal

Section: 27.4.3.7.2 [string.append], 27.4.3.7.3 [string.assign] Status: New Submitter: Jonathan Wakely Opened: 2022-01-21 Last modified: 2022-01-30

Priority: 3

View all other issues in [string.append].

View all issues with New status.

Discussion:

This will allocate temporary string, then copy one byte from it:

std::string s;
s.append("a very very long string that doesn't fit in SSO buffer", 13, 1);

The problem is that there is no overload accepting an NTBS there, so it converts to a temporary string using:

append(const basic_string&, size_t, size_t = npos);

C++17 added a new overload for a string_view:

append(const T&, size_t, size_t = npos);

But that overload is constrained to not allow const char* arguments, so we still create a temporary string.

If we're going to accept those arguments, we should do so efficiently, without forcing an unnecessary allocation.

We could do better if we split the append(const T&, ...) overload into:

append(const T&, size_t);
append(const T&, size_t, size_t);

and then remove the !is_convertible_v<const T&, const charT*> constraint from the second overload (Option A in the proposed resolution).

Alternatively, we can leave the append(const T&, size_t, size_t) overload alone and just add one taking exactly the arguments used above. That seems simpler to me, and I prefer that (Option B in the Proposed Resolution).

The same problem applies to assign("...", pos, n).

[2022-01-30; Reflector poll]

Set priority to 3 after reflector poll.

Proposed resolution:

This wording is relative to N4901.

[Drafting Note: Two mutually exclusive options are prepared, depicted below by Option A and Option B, respectively.]

Option A:

  1. Modify 27.4.3.1 [basic.string.general], class template basic_string synopsis, as indicated:

    […]
    // 27.4.3.7 [string.modifiers], modifiers
    […]
    template<class T>
      constexpr basic_string& append(const T& t);
    template<class T>
      constexpr basic_string& append(const T& t, size_type pos);
    template<class T>
      constexpr basic_string& append(const T& t, size_type pos, size_type n = npos);
    constexpr basic_string& append(const charT* s, size_type n);
    […]
    template<class T>
      constexpr basic_string& assign(const T& t);
    template<class T>
      constexpr basic_string& assign(const T& t, size_type pos);
    template<class T>
      constexpr basic_string& assign(const T& t, size_type pos, size_type n = npos);
    constexpr basic_string& assign(const charT* s, size_type n);
    […]
    
  2. Modify 27.4.3.7.2 [string.append] as indicated:

    template<class T>
      constexpr basic_string& append(const T& t);
    

    -3- Constraints:

    1. (3.1) — is_convertible_v<const T&, basic_string_view<charT, traits>> is true and

    2. (3.2) — is_convertible_v<const T&, const charT*> is false.

    -4- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return append(sv.data(), sv.size());
    
    template<class T>
      constexpr basic_string& append(const T& t, size_type pos);
    

    -?- Constraints:

    1. (?.1) — is_convertible_v<const T&, basic_string_view<charT, traits>> is true and

    2. (?.2) — is_convertible_v<const T&, const charT*> is false.

    -?- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return append(sv.substr(pos));
    
    template<class T>
      constexpr basic_string& append(const T& t, size_type pos, size_type n = npos);
    

    -5- Constraints:

    1. (5.1) — is_convertible_v<const T&, basic_string_view<charT, traits>> is true and

    2. (5.2) — is_convertible_v<const T&, const charT*> is false.

    -6- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return append(sv.substr(pos, n));
    
  3. Modify 27.4.3.7.3 [string.assign] as indicated:

    template<class T>
      constexpr basic_string& assign(const T& t);
    

    -4- Constraints:

    1. (4.1) — is_convertible_v<const T&, basic_string_view<charT, traits>> is true and

    2. (4.2) — is_convertible_v<const T&, const charT*> is false.

    -5- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return assign(sv.data(), sv.size());
    
    template<class T>
      constexpr basic_string& assign(const T& t, size_type pos);
    

    -?- Constraints:

    1. (?.1) — is_convertible_v<const T&, basic_string_view<charT, traits>> is true and

    2. (?.2) — is_convertible_v<const T&, const charT*> is false.

    -?- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return assign(sv.substr(pos));
    
    template<class T>
      constexpr basic_string& assign(const T& t, size_type pos, size_type n = npos);
    

    -6- Constraints:

    1. (6.1) — is_convertible_v<const T&, basic_string_view<charT, traits>> is true and

    2. (6.2) — is_convertible_v<const T&, const charT*> is false.

    -7- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return assign(sv.substr(pos, n));
    

Option B:

  1. Modify 27.4.3.1 [basic.string.general], class template basic_string synopsis, as indicated:

    […]
    // 27.4.3.7 [string.modifiers], modifiers
    […]
    constexpr basic_string& append(const charT* s, size_type n);
    constexpr basic_string& append(const charT* s);
    constexpr basic_string& append(const charT* s, size_type pos, size_type n);
    constexpr basic_string& append(size_type n, charT c);
    […]
    constexpr basic_string& assign(const charT* s, size_type n);
    constexpr basic_string& assign(const charT* s);
    constexpr basic_string& assign(const charT* s, size_type pos, size_type n);
    constexpr basic_string& assign(size_type n, charT c);
    […]
    
  2. Modify 27.4.3.7.2 [string.append] as indicated:

    constexpr basic_string& append(const charT* s, size_type n);
    

    -7- Preconditions: [s, s + n) is a valid range.

    -8- Effects: Appends a copy of the range [s, s + n) to the string.

    -9- Returns: *this.

    constexpr basic_string& append(const charT* s);
    

    -10- Effects: Equivalent to: return append(s, traits::length(s));

    constexpr basic_string& append(const charT* s, size_type pos, size_type n);
    

    -?- Effects: Equivalent to: return append(basic_string_view<charT, traits>(s).substr(pos, n));

  3. Modify 27.4.3.7.3 [string.assign] as indicated:

    constexpr basic_string& assign(const charT* s, size_type n);
    

    -8- Preconditions: [s, s + n) is a valid range.

    -9- Effects: Replaces the string controlled by *this with a copy of the range [s, s + n).

    -10- Returns: *this.

    constexpr basic_string& assign(const charT* s);
    

    -11- Effects: Equivalent to: return assign(s, traits::length(s));

    constexpr basic_string& assign(const charT* s, size_type pos, size_type n);
    

    -?- Effects: Equivalent to: return assign(basic_string_view<charT, traits>(s).substr(pos, n));