3663. basic_string(const T&, const Alloc&) turns moves into copies

Section: 27.4.3.3 [string.cons], 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.cons].

View all issues with New status.

Discussion:

This will do a copy not a move:

struct Str : std::string {
  Str() = default;
  Str(Str&& s) : std::string(std::move(s)) { }
};
Str s;
Str s2(std::move(s));

The problem is that the new C++17 constructor:

basic_string(const T&, const Alloc& = Alloc());

is an exact match, but the basic_string move constructor requires a derived-to-base conversion.

This is a regression since C++14, because the move constructor was called in C++14.

This problem also exists for assign(const T&) and operator=(const T&).

We can fix this by constraining those functions with !derived_from<T, basic_string>, so that the move constructor is the only viable function. Libstdc++ has done something very similar since 2017, but using !is_convertible<const T*, const basic_string*> instead of derived_from.

[2022-01-30; Reflector poll]

Set priority to 3 after reflector poll. Jonathan to revise P/R to use is_base_of or is_convertible instead of derived_from.

Proposed resolution:

This wording is relative to N4901.

  1. Modify 27.4.3.3 [string.cons] as indicated:

    template<class T>
      constexpr explicit basic_string(const T& t, const Allocator& a = Allocator());
    

    -7- Constraints:

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

    2. (7.?) — derived_from<T, basic_string> is false and

    3. (7.2) — is_convertible_v<const T&, const charT*> is false.

    -8- Effects: Creates a variable, sv, as if by basic_string_view<charT, traits> sv = t; and then behaves the same as basic_string(sv.data(), sv.size(), a).

    […]

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

    -27- Constraints:

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

    2. (27.?) — derived_from<T, basic_string> is false and

    3. (27.2) — is_convertible_v<const T&, const charT*> is false.

    -28- Effects: Equivalent to:

    basic_string_view<charT, traits> sv = t;
    return assign(sv);
    
  2. 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.?) — derived_from<T, basic_string> is false and

    3. (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());