Section: 27.4.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:
27.4.3.2 [string.require]/p4 says that basic_string
is an "allocator-aware"
container and behaves as described in 23.2.2 [container.requirements.general].
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 27.4.3.3 [string.cons]/p22 says:
basic_string<charT,traits,Allocator>& operator=(basic_string<charT,traits,Allocator>&& str) noexcept;Effects: If
*this
andstr
are not the same object, modifies*this
as shown in Table 71. [Note: A valid implementation isswap(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.
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 lengthstr.size()
whose elements are a copy of the string controlled bystr
. [ Note: A valid implementation isswap(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.
Modify the class template basic_string
synopsis in 27.4.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; […] }; }
Remove the definition of the basic_string
move assignment operator from 27.4.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
andstr
are not the same object, modifies*this
as shown in Table 71. [ Note: A valid implementation isswap(str)
. — end note ]-23- If*this
andstr
are the same object, the member has no effect.-24- Returns:*this
Table 71 —operator=(const basic_string<charT, traits, Allocator>&&)
ElementValuedata()
points at the array whose first element was pointed at bystr.data()
size()
previous value ofstr.size()
capacity()
a value at least as large assize()
Modify the paragraphs prior to 27.4.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
-3- Returns:*this = std::move(str)
.The function replaces the string controlled by*this
with a string of lengthstr.size()
whose elements are a copy of the string controlled bystr
. [ Note: A valid implementation isswap(str)
. — end note ]*this
[ 2012-08-11 Joe Gottman observes: ]
One of the effects of
basic_string
's move-assignment operator (27.4.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 likeunion { char buffer[SMALL_STRING_SIZE]; char *pdata; };When the string is small enough to fit inside
Resolution proposal: Change Table 71 to read:buffer
, thedata()
member function returnsstatic_cast<const char *>(buffer)
, and sincebuffer
is an array variable, there is no way to implement move so that the moved-to string'sbuffer
member variable is equal tothis->buffer
.
Element Value data()
points at the array whose first element was pointed at bythat contains the same characters in the same order asstr.data()
str.data()
contained beforeoperator=()
was called
[2015-05-07, Lenexa]
Howard suggests improved wording
Move to ImmediateProposed resolution:
This wording is relative to N4431.
Modify the class template basic_string
synopsis in 27.4.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); […] }; }
Change 27.4.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:
IfMove assigns as a sequence container ([container.requirements]), except that iterators, pointers and references may be invalidated.*this
andstr
are not the same object, modifies*this
as shown in Table 71. [ Note: A valid implementation isswap(str)
. — end note ]-22- If-23- Returns:*this
andstr
are the same object, the member has no effect.*this
Table 71 —operator=(basic_string&&)
effectsElementValuedata()
points at the array whose first element was pointed at bystr.data()
size()
previous value ofstr.size()
capacity()
a value at least as large assize()
Modify the paragraphs prior to 27.4.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
-4- Returns:*this = std::move(str)
.The function replaces the string controlled by*this
with a string of lengthstr.size()
whose elements are a copy of the string controlled bystr
. [ Note: A valid implementation isswap(str)
. — end note ]*this