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

4251. Move assignment for indirect unnecessarily requires copy construction

Section: 20.4.1.5 [indirect.asgn] Status: New Submitter: Jonathan Wakely Opened: 2025-05-01 Last modified: 2025-05-01

Priority: Not Prioritized

View all issues with New status.

Discussion:

The move assignment operator for indirect says:

Mandates: is_copy_constructible_t<T> is true.
However, the only way it ever construct an object is:
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.

Proposed resolution:

This wording is relative to N5008.

  1. Modify 20.4.1.5 [indirect.asgn] 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::value is false and allocator_traits<Allocator>::is_always_equal::value is false, is_copymove_constructible_t<T> is true.

    -6- Effects: If addressof(other) == this is true, there are no effects. Otherwise:

    1. (6.1) — The allocator needs updating if allocator_traits<Allocator>::propagate_on_container_move_assignment::value is true.
    2. (6.2) — If other is valueless, *this becomes valueless and the owned object in *this, if any, is destroyed using allocator_traits<Allocator>::destroy and then the storage is deallocated.
    3. (6.3) — Otherwise, if the allocator needs updating or if alloc == other.alloc is true, swaps the owned objects in *this and other; the owned object in other, if any, is then destroyed using allocator_traits<Allocator>::destroy and then the storage is deallocated *this takes ownership of the owned object of other.
    4. (6.4) — Otherwise, constructs a new owned object with the owned object of other as the argument as an rvalue, using either the allocator in *this or the allocator in other if the allocator needs updating.
    5. (6.5) — The previously owned object in *this, if any, is destroyed using allocator_traits<Allocator>::destroy and then the storage is deallocated.
    6. (6.6) — If the allocator needs updating, the allocator in *this is replaced with a copy of the allocator in other.

    -7- Postcondition: other is valueless.