This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of WP status.
indirect unnecessarily requires copy constructionSection: 20.4.1.5 [indirect.assign], 20.4.2.5 [polymorphic.assign] Status: WP Submitter: Jonathan Wakely Opened: 2025-05-01 Last modified: 2025-11-11
Priority: 1
View all issues with WP status.
Discussion:
The move assignment operator for indirect says:
Mandates:However, the only way it ever construct an object is:is_copy_constructible_t<T>istrue.
constructs a new owned object with the owned object of other as the argument
as an rvalue
and that only ever happens when alloc == other.alloc
is false.
It seems like we should require is_move_constructible_v instead,
and only if the allocator traits mean we need to construct an object.
(Technically move-constructible might not be correct, because the allocator's
construct member might use a different constructor).
Additionally, the noexcept-specifier for the move assignment doesn't match the effects. The noexcept-specifier says it can't throw if POCMA is true, but nothing in the effects says that ownership can be transferred in that case; we only do a non-throwing transfer when the allocators are equal. I think we should transfer ownership when POCMA is true, which would make the noexcept-specifier correct.
[2025-06-12; Reflector poll]
Set priority to 1 after reflector poll.
Similar change needed for std::polymorphic.
Previous resolution [SUPERSEDED]:
This wording is relative to N5008.
Modify 20.4.1.5 [indirect.assign] as indicated:
constexpr indirect& operator=(indirect&& other) noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value || allocator_traits<Allocator>::is_always_equal::value);-5- Mandates: If
allocator_traits<Allocator>::propagate_on_container_move_assignment::valueisfalseandallocator_traits<Allocator>::is_always_equal::valueisfalse,is_iscopymove_constructible_t<T>true.-6- Effects: If
addressof(other) == thisistrue, there are no effects. Otherwise:
- (6.1) — The allocator needs updating if
allocator_traits<Allocator>::propagate_on_container_move_assignment::valueistrue.- (6.2) — If
otheris valueless,*thisbecomes valuelessand the owned object in.*this, if any, is destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated- (6.3) — Otherwise, if the allocator needs updating or if
alloc == other.allocistrue,swaps the owned objects in*thisandother; the owned object inother, if any, is then destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated*thistakes ownership of the owned object ofother.- (6.4) — Otherwise, constructs a new owned object with the owned object of
otheras the argument as an rvalue, using either the allocator in*thisor the allocator inotherif the allocator needs updating.- (6.5) — The previously owned object in
*this, if any, is destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated.- (6.6) — If the allocator needs updating, the allocator in
*thisis replaced with a copy of the allocator inother.-7- Postcondition:
otheris valueless.
[2025-11-03; Tomasz provides wording.]
[Kona 2025-11-03; approved by LWG. Status changed: New → Immediate.]
[Kona 2025-11-08; Status changed: Immediate → WP.]
Proposed resolution:
This wording is relative to N5014.
Modify 20.4.1.5 [indirect.assign] as indicated:
constexpr indirect& operator=(indirect&& other) noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value || allocator_traits<Allocator>::is_always_equal::value);-5- Mandates: If
allocator_traits<Allocator>::propagate_on_container_move_assignment::valueisfalseandallocator_traits<Allocator>::is_always_equal::valueisfalse,is_iscopymove_constructible_t<T>true.-6- Effects: If
addressof(other) == thisistrue, there are no effects. Otherwise:
- (6.1) — The allocator needs updating if
allocator_traits<Allocator>::propagate_on_container_move_assignment::valueistrue.- (6.2) — If
otheris valueless,*thisbecomes valuelessand the owned object in.*this, if any, is destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated- (6.3) — Otherwise, if the allocator needs updating or if
alloc == other.allocistrue,swaps the owned objects in*thisandother; the owned object inother, if any, is then destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated*thistakes ownership of the owned object ofother.- (6.4) — Otherwise, constructs a new owned object with the owned object of
otheras the argument as an rvalue, usingeitherthe allocator in*thisor the allocator in.otherif the allocator needs updating- (6.5) — The previously owned object in
*this, if any, is destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated.- (6.6) — If the allocator needs updating, the allocator in
*thisis replaced with a copy of the allocator inother.-7- Postcondition:
otheris valueless.
Modify 20.4.2.5 [polymorphic.assign] as indicated:
constexpr polymorphic& operator=(polymorphic&& other) noexcept(allocator_traits<Allocator>::propagate_on_container_move_assignment::value || allocator_traits<Allocator>::is_always_equal::value);-5- Mandates: If
allocator_traits<Allocator>::propagate_on_container_move_assignment::valueisfalseandallocator_traits<Allocator>::is_always_equal::valueisfalse,Tis complete type.-6- Effects: If
addressof(other) == thisistrue, there are no effects. Otherwise:[…]
- (6.1) — The allocator needs updating if
allocator_traits<Allocator>::propagate_on_container_move_assignment::valueistrue.- (6.?) — If
otheris valueless,*thisbecomes valueless.- (6.2) — Otherwise, if the allocator needs updating or
Ifalloc == other.allocistrue,swaps the owned objects in*thisandother; the owned object inother, if any, is then destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated*thistakes ownership of the owned object ofother.- (6.3) —
Otherwise, ifOtherwise, constructs a new owned object with the owned object ofalloc != other.allocistrue; ifotheris not valueless, a new owned object is constructed in*thisusingallocator_traits::constructwith the owned object fromotheras the argument as an rvalue, usingeitherthe allocator in*thisor the allocator in.otherif the allocator needs updating- (6.4) — The previously owned object in
*this, if any, is destroyed usingallocator_traits<Allocator>::destroyand then the storage is deallocated.- (6.5) — If the allocator needs updating, the allocator in
*thisis replaced with a copy of the allocator inother.