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

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

Messages

Date: 2025-02-16.19:21:48

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: 2025-02-16.19:21:48

[ Hagenberg 2025-02-16; Status changed: Voting → WP. ]

Date: 2025-02-15.00:00:00

[ 2025-02-07; Reflector poll ]

Set status to Tentatively Ready after seven votes in favour during reflector poll.

"Should use parentheses not braces for the initializations." Jonathan volunteers to do that editorially after this gets approved.

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
2025-02-16 19:21:48adminsetmessages: + msg14643
2025-02-16 19:21:48adminsetstatus: voting -> wp
2025-02-07 22:49:15adminsetstatus: ready -> voting
2025-02-07 16:32:27adminsetmessages: + msg14581
2025-02-07 16:32:27adminsetstatus: new -> ready
2024-11-18 13:26:33adminsetmessages: + msg14456
2024-11-13 21:15:00adminsetmessages: + msg14454
2024-11-13 00:00:00admincreate