On Wed, 13 Sept 2023 at 16:49, Nikl Kelbon <kelbonage@gmail.com> wrote:
Where it is undefined? There are no such precondition, I quoted it

The precondition is implied by the effects. It says the mutexes are locked by a sequence of calls to lock, try_lock or unlock. After one of them is locked, calling lock on the other argument violates the precondition for lock, and calling try_lock on it violates the precondition for try_lock.

If std::lock calls lock and try_lock, then the preconditions of those functions are relevant.

Admittedly, a hostile reading of the current wording would allow locking the second mutex by calling unlock (even though that's impossible).

We could add something to make the implied precondition explicit, but phrasing it is tricky because not all mutex types have the precondition on try_lock (e.g. std::recursive_mutex does not). It basically wants to say that at every step in the algorithm, if calling lock() or try_lock() on one of the not-yet-locked arguments would be undefined, the behaviour is undefined. Which seems like a tautology to me.






ср, 13 сент. 2023 г., 17:43 Jonathan Wakely <cxx@kayari.org>:


On Wed, 13 Sept 2023 at 16:20, Nikl Kelbon via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
Postcondition of std::lock and std::try_lock:

All of Mutexes... are locked, no deadlock occurs.

Effects: All arguments are locked via a sequence of calls to lock()try_lock(), or unlock() on each argument.
 
The sequence of calls does not result in deadlock, but is otherwise unspecified.


But in an common case, when there is a type with a mutex and you need to swap it

void swap(Type& other) {
  std::scoped_lock l(this->mutex, other.mutex);
  // ... do swap ...
}

If addressof(other) == this same mutex will be locked, and there are no precondition which blocks this usage.
But in fact its undefined behavior (implementation uses try_lock on already locked mutex) (deadlock on msvc-stl, just ub on others)

There are several solutions to this, either we explicitly add precondition, or the implementation must handle this (then there could potentially be an overhead when unlocking)

Or we do nothing. The precondition already exists, whether we add it explicitly or not.

This case is the same as std::lock(m, m) which is undefined already.

Being more explicit might not hurt though.