1. Introduction
During the wording review of the C++20 Synchronization Library, [P1135R4], five design flaws were found in the paper. Rather than send the entire paper back to SG1 to look over the changes and risk missing the deadline for C++20, this new paper is being written for SG1 to review.
The wording changes here have already been applied to [P1135R5]. If SG1 approves these changes, then P1135 will go to LWG in its current state. If any of the changes are rejected by SG1, then the change will be backed out of P1135, by applying the wording change in this paper in reverse, before LWG gives its final approval to P1135.
2. Changelog
Revision 0: Initial version. Included first four changes.
Revision 1: Add a fifth change, which removes the Expects clauses on the destructors of
,
, and
. Include the results of SG1 discussion of the paper in Cologne.
3. Make atomic_flag::test const
3.1. Motivation
does not modify the
object at all,
so it should be a
member function. Similarly, the first
parameter to
and
should
be of type
or
.
This bug seems to have been here from the beginning. See [P0995R0]. There is no record of a discussion of the const-ness of these functions.
3.2. Wording
Modify the header synopsis for
in [atomics.syn] as follows:
// 30.9, flag type and operations struct atomic_flag ; bool atomic_flag_test ( const volatile atomic_flag * ) noexcept ; bool atomic_flag_test ( const atomic_flag * ) noexcept ; bool atomic_flag_test_explicit ( const volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_explicit ( const atomic_flag * , memory_order ) noexcept ;
Modify [atomics.flag] as follows:
30.9 Flag type and operations [atomics.flag]
namespace std { struct atomic_flag { bool test ( memory_order = memory_order :: seq_cst ) const volatile noexcept ; bool test ( memory_order = memory_order :: seq_cst ) const noexcept ; // ... }; bool atomic_flag_test ( const volatile atomic_flag * ) noexcept ; bool atomic_flag_test ( const atomic_flag * ) noexcept ; bool atomic_flag_test_explicit ( const volatile atomic_flag * , memory_order ) noexcept ; bool atomic_flag_test_explicit ( const atomic_flag * , memory_order ) noexcept ; //... }
Still within section [atomics.flag], change the function signatures between paragraph 4 and paragraph 5 as follows:
bool atomic_flag_test ( const volatile atomic_flag * object ) noexcept ; bool atomic_flag_test ( const atomic_flag * object ) noexcept ; bool atomic_flag_test_explicit ( const volatile atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag_test_explicit ( const atomic_flag * object , memory_order order ) noexcept ; bool atomic_flag :: test ( memory_order order = memory_order :: seq_cst ) const volatile noexcept ; bool atomic_flag :: test ( memory_order order = memory_order :: seq_cst ) const noexcept ;
4. Prohibit counting_semaphore<-1>
4.1. Motivation
template < ptrdiff_t least_max_value = implementation - defined >
class counting_semaphore ;
Template class
has a non-type template parameter
which is intended to put an upper limit on the number
of times a semaphore of that type can be simultaneously acquired.
[P1135R3] had no restrictions on the value of
.
There was nothing that prevented users from using
or
, neither of which
can do anything useful.
4.2. Wording
Insert a new paragraph after paragraph 1 in [thread.semaphore.counting.class]:
shall be greater than zero; otherwise the program is ill-formed.
least_max_value
5. Prohibit barrier::arrive(0)
5.1. Motivation
[P0666R2] and early versions of P1135 did not put any lower limit on the
value of the
parameter for
.
While working on [P1135R4], wording was added to require that
, since negative values don’t make sense. During LWG
wording review in Kona, Dan Sunderland pointed out that
would be problematic for implementations that used
a fan-in strategy rather than a counter, since it would allow threads
to wait on the barrier without arriving at the barrier.
is
of dubious usefulness even without the implementation problem, so the
lower bound of
is changed from zero to one, making
undefined behavior, the same as
.
5.2. Wording
Change paragraph 13 in [thread.coord.barrier.class] as follows:
[[ nodiscard ]] arrival_token arrive ( ptrdiff_t update = 1 ); Expects:is
update >= > 0 true
, andis less than or equal to the expected count for the current barrier phase.
update
6. Allow latch::try_wait() to fail spuriously
6.1. Motivation
The old wording for
of "Returns:
" implied that implementations needed to use
for
so that
would immediately see the result of a different thread’s call to
. The new wording that allows
to spuriously return false
frees the implementation to use a more relaxed memory order.
6.2. Wording
Change paragraph 13 in [thread.coord.latch.class] as follows:
bool try_wait () const noexcept ; Returns: With very low probabilityfalse
. Otherwise
counter == 0
7. Remove Expects clauses from destructors
7.1. Motivation
[P1135R5] had Expects clauses on the destructors of classes
,
, and
which essentially stated that no threads were blocked on the object but that some threads could still have not returned from the member functions that had blocked. That wording was a committee invention, modeled on the behavior of
, and was not based on existing practice. That wording imposes an implementation burden that was not fully understood when the wording was adopted. It would impose a cost on all users whether or not they take advantage of the additional freedom the wording grants, which goes against the principle of zero-cost overhead.
Because the wording is a requirement on the implementation, it can always be added back later if it is determined that zero-cost implementations are possible or that the cost is worth the benefit to the user of easier-to-write correct code. But if that wording goes into C++20, it would be difficult to remove it later because that would introduce undefined behavior into valid C++20 programs.
7.2. Wording
Remove paragraph 8 from [thread.semaphore.counting.class]:
~ counting_semaphore (); Expects: For every function call blocked on, a function call that will cause it to unblock and return has happened before this call. [ Note: This relaxes the usual rules, which would have required all blocking function calls to happen before destruction. — end note ]
* this
Remove paragraphs 6 and 7 from [thread.coord.latch.class]:
~ latch (); Expects: No threads are blocked on. [ Note: May be called even if some threads have not yet returned from invocations of
* this on this object, provided that they are unblocked. This relaxes the usual rules, which would have required all blocking function calls to happen before destruction. - end note ]
wait Remarks: The destructor may block until all threads have exited invocations ofon this object.
wait
Remove paragraps 11 and 12 from [thread.coord.barrier.class]:
~ barrier (); Expects: No threads are blocked at a phase synchronization point for any barrier phase of this object. [ Note: May be called even if some threads have not yet returned from invocations of, provided that they have unblocked. This relaxes the usual rules, which would have required all blocking function calls to happen before destruction. - end note ]
wait Remarks: The destructor may block until all threads have exited invocations ofon this object.
wait
8. Review by SG1
This paper was reviewed by SG1 in Cologne.
When discussing the restriction on
's
, the committee decided that
should be allowed even though the type can’t be used in any meaningful way. But everyone agreed that
should be forbidden. As a result, this wording change was made to [P1135R6]:
shall be greater than or equal to zero; otherwise the program is ill-formed.
least_max_value
There was lots of discussion about the change to remove the Expects clauses of the destructors, but the committee ended up approving that change as it was presented. So no further changes are needed to P1135.
With the change to the lower limit of
, SG1 strongly approved the paper.