Title
basic_string(const T&, const Alloc&) turns moves into copies
Status
new
Section
[string.cons][string.assign]
Submitter
Jonathan Wakely

Created on 2022-01-21.00:00:00 last changed 35 months ago

Messages

Date: 2022-01-30.17:05:36

Proposed resolution:

This wording is relative to N4901.

  1. Modify [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 [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());
    
Date: 2022-01-15.00:00:00

[ 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.

Date: 2022-01-21.00:00:00

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.

History
Date User Action Args
2022-01-30 17:05:36adminsetmessages: + msg12331
2022-01-22 16:22:48adminsetmessages: + msg12278
2022-01-21 00:00:00admincreate