Title
unique_lock self-move-assignment is broken
Status
new
Section
[thread.lock.unique.cons] [thread.lock.shared.cons]
Submitter
Casey Carter

Created on 2024-11-13.00:00:00 last changed 2 days ago

Messages

Date: 2024-11-18.13:26:33

Proposed resolution:

This wording is relative to N4993.

Drafting Note: I've chosen to use the move-into-temporary-and-swap idiom here to keep things short and sweet. Since move construction, swap, and destruction are all `noexcept`, I've promoted move assignment from "Throws: Nothing" to `noexcept` as well. This is consistent with eliminating the implicit narrow contract condition that `*this` and `u` refer to distinct objects.
  1. In the class synopsis in [thread.lock.unique.general], annotate the move assignment operator as `noexcept`:

    
      namespace std {
        template<class Mutex>
        class unique_lock {
          [...]
          unique_lock& operator=(unique_lock&& u) noexcept;
          [...]
        };
      }
    
  2. Modify [thread.lock.unique.cons] as follows:

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

    -18- Effects: If `owns` calls `pm->unlock()`. Equivalent to: `unique_lock{std::move(u)}.swap(*this)`.

    -?- Returns: `*this`.

    -19- Postconditions: `pm == u_p.pm` and `owns == u_p.owns` (where `u_p` is the state of `u` just prior to this construction), `u.pm == 0` and `u.owns == false`.

    -20- [Note 1: With a recursive mutex it is possible for both `*this` and u to own the same mutex before the assignment. In this case, *this will own the mutex after the assignment and u will not. — end note]

    -21- Throws: Nothing.

  3. Modify [thread.lock.shared.cons] as follows:

    
    shared_lock& operator=(shared_lock&& sl) noexcept;
    

    -17- Effects: If `owns` calls `pm->unlock_shared()`. Equivalent to: `shared_lock{std::move(sl)}.swap(*this)`.

    -?- Returns: `*this`.

    -18- Postconditions: `pm == sl_p.pm` and `owns == sl_p.owns` (where `sl_p` is the state of `sl` just prior to this assignment), `sl.pm == nullptr` and `sl.owns == false`.

Date: 2024-11-15.00:00:00

[ 2024-11-18; Casey expands the PR to cover `shared_lock` ]

`shared_lock` has the same problems, and can be fixed in the same way.

Date: 2024-11-13.00:00:00

The postconditions in [thread.lock.unique.cons] paragraph 19:

Postconditions: `pm == u_p.pm` and `owns == u_p.owns` (where `u_p` is the state of `u` just prior to this construction), `u.pm == 0` and `u.owns == false`.
contradict themselves if `*this` and the parameter `u` refer to the same object. (Presumably "this construction" means the assignment, and it is copy-pasta from the move constructor postconditions.) Apparently `unique_lock` didn't get the memo that we require well-defined behavior from self-move-assignment as of LWG 2839.

Also, the move assignment operator doesn't specify what it returns.

History
Date User Action Args
2024-11-18 13:26:33adminsetmessages: + msg14456
2024-11-13 21:15:00adminsetmessages: + msg14454
2024-11-13 00:00:00admincreate