This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of C++17 status.

### 2063. Contradictory requirements for string move assignment

Section: 21.3.3 [basic.string] Status: C++17 Submitter: Howard Hinnant Opened: 2011-05-29 Last modified: 2017-07-30

Priority: 3

View other active issues in [basic.string].

View all other issues in [basic.string].

View all issues with C++17 status.

Discussion:

21.3.3.2 [string.require]/p4 says that `basic_string` is an "allocator-aware" container and behaves as described in 22.2.1 [container.requirements.general].

22.2.1 [container.requirements.general] describes move assignment in p7 and Table 99.

If `allocator_traits<allocator_type>::propagate_on_container_move_assignment::value` is false, and if the allocators stored in the lhs and rhs sides are not equal, then move assigning a string has the same semantics as copy assigning a string as far as resources are concerned (resources can not be transferred). And in this event, the lhs may have to acquire resources to gain sufficient capacity to store a copy of the rhs.

However 21.3.3.3 [string.cons]/p22 says:

>     basic_string<charT,traits,Allocator>&
>     operator=(basic_string<charT,traits,Allocator>&& str) noexcept;

> > Effects: If `*this` and `str` are not the same object, modifies `*this` as shown in Table 71. [Note: A valid implementation is `swap(str)`. — end note ]

These two specifications for `basic_string::operator=(basic_string&&)` are in conflict with each other. It is not possible to implement a `basic_string` which satisfies both requirements.

Additionally assign from an rvalue `basic_string` is defined as:

>     basic_string& assign(basic_string&& str) noexcept;

> > Effects: The function replaces the string controlled by `*this` with a string of length `str.size()` whose elements are a copy of the string controlled by `str`. [ Note: A valid implementation is `swap(str)`. — end note ]

It seems contradictory that this member can be sensitive to `propagate_on_container_swap` instead of `propagate_on_container_move_assignment`. Indeed, there is a very subtle chance for undefined behavior here: If the implementation implements this in terms of `swap`, and if `propagate_on_container_swap` is false, and if the two allocators are unequal, the behavior is undefined, and will likely lead to memory corruption. That's a lot to go wrong under a member named "assign".

[ 2011 Bloomington ]

Alisdair: Can this be conditional `noexcept`?

Pablo: We said we were not going to put in many conditional `noexcept`s. Problem is not allocator, but non-normative definition. It says swap is a valid operation which it is not.

Dave: Move assignment is not a critical method.

Alisdair: Was confusing assignment and construction.

Dave: Move construction is critical for efficiency.

Kyle: Is it possible to test for `noexcept`.

Alisdair: Yes, query the `noexcept` operator.

Alisdair: Agreed there is a problem that we cannot unconditionally mark these operations as `noexcept`.

Pablo: How come swap is not defined in alloc

Alisdair: It is in utility.

Pablo: Swap has a conditional `noexcept`. Is no throw move constructable, is no throw move assignable.

Pablo: Not critical for strings or containers.

Kyle: Why?

Pablo: They do not use the default swap.

Dave: Important for deduction in other types.

Alisdair: Would change the policy we adopted during FDIS mode.

Pablo: Keep it simple and get some vendor experience.

Alisdair: Is this wording correct? Concerned with bullet 2.

Pablo: Where does it reference containers section.

Alisdair: String is a container.

Alisdair: We should not remove redundancy piecemeal.

Pablo: I agree. This is a deviation from rest of string. Missing forward reference to containers section.

Pablo: To fix section 2. Only the note needs to be removed. The rest needs to be a forward reference to containers.

Alisdair: That is a new issue.

Pablo: Not really. Talking about adding one sentence, saying that basic string is a container.

Dave: That is not just a forward reference, it is a semantic change.

PJ: We intended to make it look like a container, but it did not satisfy all the requirements.

Pablo: Clause 1 is correct. Clause 2 is removing note and `noexcept` (do not remove the rest). Clause 3 is correct.

Alisdair: Not sure data() is correct (in clause 2).

Conclusion: Move to open, Alisdair and Pablo volunteered to provide wording

[ originally proposed wording: ]

This wording is relative to the FDIS.

1. Modify the class template `basic_string` synopsis in 21.3.3 [basic.string]:

   >     namespace std {
   >       template<class charT, class traits = char_traits<charT>,
   >         class Allocator = allocator<charT> >
   >       class basic_string {
   >       public:
   >         […]
   >         basic_string& operator=(basic_string&& str) noexcept;
   >         […]
   >         basic_string& assign(basic_string&& str) noexcept;
   >         […]
   >       };
   >     }

2. Remove the definition of the `basic_string` move assignment operator from 21.3.3.3 [string.cons] entirely, including Table 71 — `operator=(const basic_string<charT, traits, Allocator>&&)`. This is consistent with how we define move assignment for the containers in Clause 23:

   >     basic_string<charT,traits,Allocator>&
   >     operator=(basic_string<charT,traits,Allocator>&& str) noexcept;

   > > -22- Effects: If `*this` and `str` are not the same object, modifies `*this` as shown in Table 71. [ Note: A valid implementation is `swap(str)`. — end note ]

   > > -23- If `*this` and `str` are the same object, the member has no effect.

   > > -24- Returns: `*this`

   > Table 71 — `operator=(const basic_string<charT, traits, Allocator>&&)`
   > Element | Value
   > --- | ---
   > `data()` | points at the array whose first element was pointed at by `str.data()`
   > `size()` | previous value of `str.size()`
   > `capacity()` | a value at least as large as `size()`

3. Modify the paragraphs prior to 21.3.3.7.3 [string.assign] p.3 as indicated (The first insertion recommends a separate paragraph number for the indicated paragraph):

   >     basic_string& assign(basic_string&& str) noexcept;

   > > -?- Effects: Equivalent to `*this = std::move(str)`. The function replaces the string controlled by `*this` with a string of length `str.size()` whose elements are a copy of the string controlled by `str`. [ Note: A valid implementation is `swap(str)`. — end note ]

   > > -3- Returns: `*this`

[ 2012-08-11 Joe Gottman observes: ]

> One of the effects of `basic_string`'s move-assignment operator (21.3.3.3 [string.cons], Table 71) is

> > Element | Value
> > --- | ---
> > `data()` | points at the array whose first element was pointed at by `str.data()`

> If a string implementation uses the small-string optimization and the input string `str` is small enough to make use of it, this effect is impossible to achieve. To use the small string optimization, a string has to be implemented using something like

> >     union
> >     {
> >        char buffer[SMALL_STRING_SIZE];
> >        char *pdata;
> >     };

> When the string is small enough to fit inside `buffer`, the `data()` member function returns `static_cast<const char *>(buffer)`, and since `buffer` is an array variable, there is no way to implement move so that the moved-to string's `buffer` member variable is equal to `this->buffer`.

> Resolution proposal:

> Change Table 71 to read:

> > Element | Value
> > --- | ---
> > `data()` | points at the array whose first element was pointed at by `str.data()` that contains the same characters in the same order as `str.data()` contained before `operator=()` was called

[2015-05-07, Lenexa]

Howard suggests improved wording

Move to Immediate

Proposed resolution:

This wording is relative to N4431.

1. Modify the class template `basic_string` synopsis in 21.3.3 [basic.string]:

   >     namespace std {
   >       template<class charT, class traits = char_traits<charT>,
   >         class Allocator = allocator<charT> >
   >       class basic_string {
   >       public:
   >         […]
   >         basic_string& assign(basic_string&& str) noexcept(
   >              allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
   >                allocator_traits<Allocator>::is_always_equal::value);
   >         […]
   >       };
   >     }

2. Change 21.3.3.3 [string.cons]/p21-23:

   >     basic_string&
   >     operator=(basic_string&& str) noexcept(
   >              allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
   >                allocator_traits<Allocator>::is_always_equal::value);

   > > -21- Effects: If `*this` and `str` are not the same object, modifies `*this` as shown in Table 71. [ Note: A valid implementation is `swap(str)`. — end note ] Move assigns as a sequence container ([container.requirements]), except that iterators, pointers and references may be invalidated.

   > > -22- If `*this` and `str` are the same object, the member has no effect.

   > > -23- Returns: `*this`

   > Table 71 — `operator=(basic_string&&)` effects
   > Element | Value
   > --- | ---
   > `data()` | points at the array whose first element was pointed at by `str.data()`
   > `size()` | previous value of `str.size()`
   > `capacity()` | a value at least as large as `size()`

3. Modify the paragraphs prior to 21.3.3.7.3 [string.assign] p.3 as indicated

   >     basic_string& assign(basic_string&& str) noexcept(
   >              allocator_traits<Allocator>::propagate_on_container_move_assignment::value ||
   >                allocator_traits<Allocator>::is_always_equal::value);

   > > -3- Effects: Equivalent to `*this = std::move(str)`. The function replaces the string controlled by `*this` with a string of length `str.size()` whose elements are a copy of the string controlled by `str`. [ Note: A valid implementation is `swap(str)`. — end note ]

   > > -4- Returns: `*this`