3455. Incorrect Postconditions on unique_ptr move assignment

Section: 20.3.1.3.4 [unique.ptr.single.asgn] Status: C++23 Submitter: Howard Hinnant Opened: 2020-06-22 Last modified: 2023-11-22

Priority: 0

View all other issues in [unique.ptr.single.asgn].

View all issues with C++23 status.

Discussion:

20.3.1.3.4 [unique.ptr.single.asgn]/p5 says this about the unique_ptr move assignment operator:

Postconditions: u.get() == nullptr.

But this is only true if this != &u. For example:

#include <iostream>
#include <memory>

int main()
{
  auto x = std::unique_ptr<int>(new int{3});
  x = std::move(x);
  if (x)
    std::cout << *x << '\n';
  else
    std::cout << "nullptr\n";
}

Output:

3

An alternative resolution to that proposed below is to just delete the Postcondition altogether as the Effects element completely specifies everything. If we do that, then we should also remove p10, the Postconditions element for the converting move assignment operator. I have a slight preference for the proposed change below as it is more informative, at the expense of being a little more repetitive.

[2020-06-26; Reflector prioritization]

Set priority to 0 and status to Tentatively Ready after seven votes in favor during reflector discussions.

[2020-11-09 Approved In November virtual meeting. Status changed: Tentatively Ready → WP.]

Proposed resolution:

This wording is relative to N4861.

  1. Modify 20.3.1.3.4 [unique.ptr.single.asgn] as indicated:

    unique_ptr& operator=(unique_ptr&& u) noexcept;
    

    -1- Constraints: is_move_assignable_v<D> is true.

    -2- Preconditions: If D is not a reference type, D meets the Cpp17MoveAssignable requirements (Table [tab:cpp17.moveassignable]) and assignment of the deleter from an rvalue of type D does not throw an exception. Otherwise, D is a reference type; remove_reference_t<D> meets the Cpp17CopyAssignable requirements and assignment of the deleter from an lvalue of type D does not throw an exception.

    -3- Effects: Calls reset(u.release()) followed by get_deleter() = std::forward<D>(u.get_deleter()).

    -4- Returns: *this.

    -5- Postconditions: If this != addressof(u), u.get() == nullptr, otherwise u.get() is unchanged.