---
title: "Single Ends for C++ Concurrent Queues"
document: P4295R0
date: 2026-07-07
audience: SG1, LEWG
reply-to:
  - "Detlef Vollmann <dv@vollmann.ch>"
---

## Revision History

This paper is the initial revision.

## Introduction

[P0260](https://wg21.link/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](https://wg21.link/n3353) and was removed by SG1 to make it a separate class.

## Implementation

A partial implementation is available at [gitlab.com/cppzs/bounded-queue](https://gitlab.com/cppzs/bounded-queue).

## 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);
};
```

### 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;
```

### Lifetime

#### 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.

#### 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>);
```

## 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.

## Proposed Wording

This wording is currently incomplete!!!

### Concurrent Queue Ends [conqueues.ends]

#### 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;
}
```

#### Class template `queue_front` [queue.end.front]

1. `queue_front` provides the pop interface for a concurrent queue.
```
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](https://wg21.link/n3353). 14 January 2012. URL: [https://wg21.link/n3353](https://wg21.link/n3353)
[P0260]
   [P0260](https://wg21.link/P0260). URL: [https://wg21.link/P0260](https://wg21.link/P0260)**
