P4295R0
Single Ends for C++ Concurrent Queues

Published Proposal,

This version:
http://wg21.link/P4295R0
Author:
Audience:
SG1, LEWG
Project:
ISO/IEC 14882 Programming Languages — C++, ISO/IEC JTC1/SC22/WG21
Source:
gitlab.com/cppzs/papers/-/blob/main/source/P4295R0.bs

Abstract

Bounded concurrent queues are an important communication mechanism. But in most cases a specific part in a program only needs acces to one end of the queue. This paper proposes two templates that works with any concurrent_queue P0260 that provide only one part of the queue interface.

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, queue_front and queue_back:

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 queue_front and queue_back 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 shared_ptr 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]

  1. queue_front provides the pop interface for a concurrent queue.

  2. 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>;
    
    

References

Non-Normative References

[N3353]
Lawrence Crowl. C++ Concurrent Queues. 14 January 2012. URL: https://wg21.link/n3353
[P0260]
P0260. URL: https://wg21.link/P0260