1. Revision History
This paper is the initial revision.
2. Introduction
P0260 proposes concurent queue concepts as communication mechanism for concurrent systems. This communication is typically uni-directional, i.e. a specifc part of a system is either a producer and therefore only needs the pushing interface, or it is a consumer and only needs access to the pop interface.
So it helps code structure to provide a helper that gives access to only one end of a queue.
The single ended interfaces for concurrent queues was in the very first concurrent queue proposal C++ Concurrent Queues and was removed by SG1 to make it a separate class.
3. Implementation
A partial implementation is available at gitlab.com/cppzs/bounded-queue.
4. Design
The basic design of a queue end helper if given by the queue it wraps:
template < basic_concurrent_queue QType > class queue_front { public : typedef typename QType :: value_type value_type ; std :: optional < value_type > pop (); std :: expected < value_type , conqueue_status > try_pop () requires concurrent_queue < QType > ; sender auto async_pop () requires async_concurrent_queue < QType > ; };
and
template < basic_concurrent_queue QType > class queue_back { public : bool push ( T && x ); template < class ... Args > bool emplace ( Args && ... as ); conqueue_status try_push ( T && x ) requires concurrent_queue < QType > ; template < class ... Args > conqueue_status try_emplace ( Args && ... as ) requires concurrent_queue < QType > ; sender auto async_push ( T && x ) requires concurrent_queue < QType > ; template < class ... Args > sender auto async_emplace ( Args && ... as ); };
4.1. Closing Interface
It depends on the specific usage scenario of a queue who’s responsible for closing a queue (if at all).
Sometimes it’s a producer, sometimes it’s a consumer, and sometimes it’s some outside controller.
So it makes sense to provide the interface related to closing
to both ends, and :
bool is_closed () const noexcept ; void close () noexcept ;
4.2. Lifetime
4.2.1. External
A queue must outlive any end object that’s based on it.
In many cases this is guaranteed by external program logic.
The simple templates and take
a reference to their underlying queue in their constructors:
explicit queue_front ( QType & qu ); explicit queue_back ( QType & qu );
Using a queue end when the underlying queue was destructed is undefined.
4.2.2. Managed
Sometimes some kind of automated lifetime management is more useful, where the underlying queue lives as long as an end to it exists.
This paper proposes a second pair of templates that manages the lifetime of the queue in this way.
Their constructors take a to an existing queue.
explicit managed_queue_front ( shared_ptr < QType > ); explicit managed_queue_back ( shared_ptr < QType > );
5. More Interface
The queue type of the underlying queue is provided in the queue end:
using queue_type = QType ;
Queue ends are essentially reference types, so they’re copyable and movable.
6. Proposed Wording
This wording is currently incomplete!!!
6.1. Concurrent Queue Ends [conqueues.ends]
6.1.1. Header < conqueue > synopsis [conqueues.syn]
namespace std { template < basic_concurrent_queue QType > class queue_front ; template < basic_concurrent_queue QType > class queue_back ; template < basic_concurrent_queue QType > class managed_queue_front ; template < basic_concurrent_queue QType > class managed_queue_back ; }
6.1.2. Class template queue_front [queue.end.front]
-
provides the pop interface for a concurrent queue.queue_front -
template < basic_concurrent_queue QType > class queue_front { public : using queue_type = QType ; explicit queue_front ( QType & qu ); // observers bool is_closed () const noexcept ; // modifiers void close () noexcept ; std :: optional < typename queue_type :: value_type > pop (); std :: expected < typename queue_type :: value_type , conqueue_status > try_pop () requires concurrent_queue < QType > ; execution :: sender auto async_pop () requires async_concurrent_queue < QType > ; }; template < basic_concurrent_queue QType > class queue_back { public : using queue_type = QType ; explicit queue_back ( QType & qu ); // observers bool is_closed () const noexcept ; // modifiers void close () noexcept ; template < class Vt > bool push ( Vt && x ); template < class ... Args > bool emplace ( Args && ... as ); template < class Vt > conqueue_status try_push ( Vt && x ) requires concurrent_queue < QType > ; template < class ... Args > conqueue_status try_emplace ( Args && ... as ) requires concurrent_queue < QType > ; template < class Vt > execution :: sender auto async_push ( Vt && x ) requires concurrent_queue < QType > ; template < class ... Args > execution :: sender auto async_emplace ( Args && ... as ) requires concurrent_queue < QType > ;