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.