This document offers a complete reference for all public types and functions in Copper.
All elements are contained within the namespace copper
, which is implicitly omitted in this reference documents.
Function signatures shown in this document are not necessarily the actual implementation in code. They only represent their behavior and their usage, while striving to be more readable as a reference compared to the actual lengthy definitions.
Channels are the core type of Copper.
They represent a queue objects, sending (or "pushing") messages of type T
from one direction to another, where they are received (or "popped").
template<
bool is_buffered,
typename T,
template<typename...> typename BufferQueue = std::queue,
template<typename...> typename OpQueue = std::deque
>
class channel;
is_buffered
distinguishes buffered and unbuffered channels.
There are type aliases copper::buffered_channel
and copper::unbuffered_channel
expecting the same template arguments, except for is_buffered
.
See the sections below for more details on their differences.
T
is the type of the messages that are passed by this channel.
It needs to be default constructible, move constructible, move assignable, and destructible.
The only exception is T = void
.
See Void channels for more details.
BufferQueue
is the type used for the internal buffer of messages.
It is only used when is_buffered
is true
and T
is not void
.
It needs to satisfy basic operations on a queue like std::queue
does; specifically, it needs to support push
, pop
, and front
.
OpQueue
is the type of the container used to store pending operations on a channel.
In addition to being used as a queue, it also needs to support erasing elements at arbitrary positions.
It defaults to std::dequeue
.
Buffered channels contain an internal queue to store messages for later use.
That means a message can be pushed into the channel at an arbitrary point before it will be returned by a pop again later.
They are the more "general use" choice of the two and provide speed comparable to that of a simple std::queue
in combination with a mutex.
// Create a new channel with an unlimited buffer.
auto chan = buffered_channel<int>();
// Create a new buffered channel that can store up to 10 messages.
auto chan = buffered_channel<int>(10);
Unbuffered channels do not store any T
elements within themselves.
Rather, a push or pop operation in one thread blocks until its counterpart is executed at the same time by another thread.
Unbuffered channels are more difficult to use and slower than buffered channels in general.
See docs/motivation.adoc for information on what advantages an unbuffered channel can offer, besides the obvious smaller memory footprint.
// Create a new channel without a buffer.
auto chan = unbuffered_channel<int>();
A void channel is a channel with message type T = void
.
They are used as "signals", similar to std::future<void>
.
Pushing into a void channel can be seen as emitting a signal and popping from it then consumes the signal.
Void channels can be buffered or unbuffered and support all the same operations that normal channels support, with a few differences.
In general, any function that would return std::optional<T>
returns bool
instead.
Any function that has a T
parameter is missing that parameter instead.
The same is true for callables passed to copper functions, such as select
or push_func
.
A channel can have two states, open and closed, always starting in the former when constructed. A channel that is open can be freely used to pass messages. A closed channel does not allow any further push operations but if it is buffered, the remaining messages can still be popped.
Most operations on channels belong into one of the "push family" or the "pop family", which are used to send and receive messages respectively.
In its most basic form, these two families are represented by the functions push
and pop
:
// Send the message "1" over this channel "chan".
const bool push_successful = chan.push(1);
// Consume the message.
const std::optional<int> pop_result = chan.pop();
push
accepts a value that can be converted to T
and returns a bool
that is true
if the operation could be completed successfully.
This is not the case if the channel is closed, in which case nothing can be pushed into it and false
is returned.
pop
returns an std::optional<T>
which contains the next message from the queue if it could be executed successfully.
If the channel is closed and there are no messages in the buffer of the channel, pop
will return std::nullopt
.
Both push
and pop
will block the caller as long as needed if they cannot complete their operation immediately.
This could be because the internal buffer is full / empty or the channel is simply unbuffered.
Only when the operation can be completed or the channel is closed and the operation has to be canceled will the call unblock.
Copper ensures that blocked operations are treated in a FIFO order. For example, if two or more "push" operations are pending on a channel with a full buffer, as soon as a "pop" operation is run, the "push" that has been waiting the longest is guaranteed to be executed first.
Both push
and pop
offer three variants for non-blocking access.
bool push(T);
bool try_push(T);
bool try_push_for(T, const std::chrono::duration&);
bool try_push_until(T, const std::chrono::time_point&);
std::optional<T> pop();
std::optional<T> try_pop();
std::optional<T> try_pop_for(std::chrono::duration&);
std::optional<T> try_pop_until(const std::chrono::time_point&);
This pattern of the four variants of one operation can be found in several places in Copper. They always have the same behavior:
-
X()
blocks the caller until the operation either can be completed successfully or a state is reached in which it is impossible to be completed anymore. -
try_X()
does not block the caller at all. It only executes the operation if it can be done immediately. -
try_X_for(const std::chrono::duration&)
behaves likeX()
with the addition of a timeout. If the call could not execute the operation and has blocked the caller for at least the given duration, it is cancelled and returns to the caller. -
try_X_until(const std::chrono::time_point&)
behaves liketry_X_for
, only that instead of a duration, a fixed point in time is passed. The operation is cancelled as soon as the system clock passes that point.
Like push
, the other three "push" functions return true
only if the operation could be completed successfully.
In their case, that means false
can be returned even if the channel is still open, when a timeout occurs.
The same is true for the "pop" functions and std::nullopt
.
push
and pop
provide easy access to the basic functionality of a channel while sacrificing some efficiency and power.
Full access is gained by using push_func
/ pop_func
and their variants.
channel_op_status push_func(const std::function<T()>&);
channel_op_status try_push_func(const std::function<T()>&);
channel_op_status try_push_func_for(const std::function<T()>&, const std::chrono::duration&);
channel_op_status try_push_func_until(const std::function<T()>&, const std::chrono::time_point&);
channel_op_status pop_func(const std::function<void(T&&)>&);
channel_op_status try_pop_func(const std::function<void(T&&)>&);
channel_op_status try_pop_func_for(const std::function<void(T&&)>&, const std::chrono::duration&);
channel_op_status try_pop_func_until(const std::function<void(T&&)>&, const std::chrono::time_point&);
Instead of expecting a T
object, push_func
uses a callable argument which is used as a generator for the message to be pushed.
If the push operation can be completed successfully, and only then, is the passed function called and its return value is sent over the channel.
Instead of returning a T
object, pop_func
also uses a callable argument which handles the popped message.
If the pop operation can be completed successfully, the argument is called with the received message.
All functions return a channel_op_status
value, which is a scoped enum.
enum class channel_op_status {
success, /** The operation was completed successfully. */
unavailable, /** The operation could not be completed due to resources being unavailable. */
closed, /** The operation could not be completed, as the channel object is already closed. */
};
Instead of a binary value (success / no success), this represents a ternary result, where "no success" is split into closed
, meaning the channel is closed, and the operation cannot be executed on that channel anymore, or unavailable
, meaning the operation could not be executed at the moment and caused a timeout.
Important
|
The channel object is locked while a callable is executed. This can cause unexpected slowdowns or even deadlocks in certain cases. See docs/techdetails.adoc for details. |
Copper also defines the following scoped enum, which represents the four variants of each operation seen so far:
enum class wait_type {
forever, /** Wait for however long it takes to complete. */
none, /** Do not wait at all, if the operation cannot be completed immediately. */
for_, /** Wait for a set amount of time. */
until, /** Wait until a certain point in time. */
};
Instead of distinguishing the variant by name, a channel also offers functions to access the different behavior by template parameter.
template<wait_type>
std::optional<T> pop_wt(Args&&...);
template<wait_type>
bool push_wt(T, Args&&...);
template<wait_type>
channel_op_status pop_func_wt(const std::function<T()>&, Args&&...);
template<wait_type>
channel_op_status push_func_wt(const std::function<void(T&&)>&, Args&&...);
In general, you probably do not want to use these "_wt" functions for direct calls, as they are much less readable than the named operations. They can be useful however when another templated function calls one of these operations.
chan.template push_wt<wait_type::for_>(123, 200ms);
Channels offer a few functions apart from the "push" and "pop" family.
channel_pop_iterator<channel> begin();
channel_pop_iterator<channel> end();
channel_push_iterator<channel> push_iterator();
channel_read_view<channel> read_view();
channel_write_view<channel> write_view();
The functions begin
, end
, push_iterator
, read_view
, and write_view
provide easy access to iterators and views on this channel.
See Views and Iterators for more information on that.
void close();
bool is_read_closed();
bool is_write_closed();
size_t clear();
The close
function does just that: it closes the channel, preventing any further "push" operations.
is_write_closed
returns true
if and only if the channel is closed, meaning that no more messages can be written to it. is_write_closed
returns true
if and only if the channel is closed and empty, meaning that no more messages can be read from it.
clear
empties the buffer of the channel and returns the number of messages that were cleared.
This is probably the most situational of all functions in channel
.
It is recommended to not use clear
on an open channel, as pending "push" operations are not cancelled and the behavior might be unexpected.
It can be used after closing a buffered channel to instantly stop all consumers, instead of them processing the rest of the buffer.
While channels by themselves can suffice to provide communication between threads, the "select" functions add to their potential. They allow you to combine the handling of multiple channels into a single thread, thus reducing the total number of threads and synchronisation objects and making your code more cohesive.
channel_op_status select(Ops&&...);
channel_op_status try_select(Ops&&...);
channel_op_status try_select_for(Ops&&..., const std::chrono::duration&);
channel_op_status try_select_until(Ops&&..., const std::chrono::time_point&);
A "select" can be seen as the parallel execution of one or multiple "push_func" and/or "pop_func" operations.
The Ops
type parameter defines that group of operations, which are created by the shift operators <<
and >>
(inspired by Go’s ->
and <-
).
auto operator>>(channel&, const std::function<void(T&&)>&);
auto operator<<(channel&, const std::function<T()>&);
>>
("something being moved from the channel") represents the "pop". <<
("something being moved into the channel") represents the "push".
// These two statements are equivalent:
chan.pop_func([](int i) { consume(i); });
try_select(chan >> [](int i) { consume(i); });
While a single-op select can be represented by channel member functions, that is not possible for multi-op selects anymore.
// Consume a message from either chan1 or chan2. (not both!)
try_select_for(
1s,
chan1 >> [](std::string s) { consume(std::move(s)); },
chan2 >> [](int i) { consume(i); }
);
You can also use the same channel multiple times and mix pushes and pops within one "select".
select(
chan1 << [] { return std::string("Hello World!"); },
chan1 >> [](std::string s) { consume(std::move(s)); },
chan2 >> [](int i) { chan1.push(std::to_string(i)); }
);
A "select" guarantees that:
-
No deadlocks occur, even if a consumer or producer function accesses the channel itself again. Note that this only holds true as long as the passed callable does not cause deadlocks itself.
-
At most one select operation will be chosen and executed for each "select" statement.
-
If a "select" statement is called when at least one operation can be executed immediately, the operation that will be executed is chosen uniform randomly from all ready operations to ensure fairness.
A select statement returns
-
channel_op_status::success
, if any one of the operations could be executed. -
channel_op_status::closed
, if the channels of all operations are closed and the channels of all push operations are empty. -
channel_op_status::unavailable
otherwise.
Important
|
The channel object is locked while a callable is executed. This can cause unexpected slowdowns or even deadlocks in certain cases. See docs/techdetails.adoc for details. |
Due to the locking behavior described above, there is an alternative "vselect" family which can prove to be faster when a lot of time is spent within the "select" callbacks.
A "vselect" operation runs the push and pop on values rather than functions, similar to channel::push
and channel::pop
compared to their related "*_func" functions.
To be able to be used similar to a switch-case
statement, a "vselect" also accepts callback functions.
The difference to "select" is that the channel can be unlocked while these are executed.
vselect(
chan1 << 1, // Push "1" into chan1, if possible.
[] {},
chan2 << std::move(x), // Push the value of "x" into chan2, if possible.
[&x] { x = compute_next_value(); }, // If chan2 was pushed into, "x" is re-computed.
chan3 >> y, // Pop the next message of chan3 into "y".
[] {}
);
For void channels, there is a special placeholder value to replace the variables and values.
The placeholder can be accessed under the name copper::_
or copper::voidval
.
using copper::_;
vselect(
chan1 << _, // Send a message over the void channel if possible.
[] {},
// If "chan2" contained a message, the application exists.
chan2 >> _,
[] { exit(1); },
);
Views are references to existing channels that allow only a subset of all operations. channel_read_view
only allows "pop" functions as well as begin()
and end()
.
channel_write_view
only allows "push" functions as well as push_iterator()
, close()
, and clear()
.
auto read_view1 = channel_read_view(chan);
// equivalent to:
auto read_view2 = chan.read_view();
auto write_view1 = channel_write_view(chan);
// equivalent to:
auto write_view2 = chan.write_view();
Views are useful when a library or framework that uses channels to communicate allows a user to provide messages that will then be consumed by a library-internal thread.
Passing the user a channel_write_view
instead of a channel&
prevents any accidental misuse by trying to pop from the channel rather than push into it.
Copper provides two type of iterator types: channel_pop_iterator
and channel_push_iterator
.
They can be instantiated for every channel with non-void messages.
channel_pop_iterator
is a std::input_iterator
that pops elements from the underlying channel when incremented.
It uses pop
to extract elements, meaning it will block until an element can be popped successfully.
It is considered to have reached its end when the channel is closed and contains no buffered messages.
A channel_pop_iterator
can be constructed most easily by calling begin()
and end()
on a channel.
Because of that, channels can not only be used in combination with std::algorithm
but also classify as ranges in the context of std::ranges
.
It can also be used in a for-each loop.
// Does the producer send a "1" at any point?
std::find(chan.begin(), chan.end(), 1) != chan.end();
std::ranges::find(chan, 1) != chan.end();
// Do something until the channel is closed.
for (auto message : chan) {
// ...
}
channel_push_iterator
works similarly to std::back_insert_iterator
.
The statement *iter = val;
is equivalent to chan.push(val);
.
It can be constructed most easily by calling push_iterator()
on a channel.
While having less use cases than the channel_pop_iterator
, a channel_push_iterator
also allows support for some more std
algorithms.
// Push all elements from a vector into my channel.
std::copy(v.begin(), v.end(), c.push_iterator());
std::ranges::copy(v, c.push_iterator());
Copper offers some slight configuration options for different use cases.
By default, Copper uses std::recursive_mutex
in some places to make sure that referencing a channel within a "push_func", "pop_func", or "select" will not causea deadlock.
chan.pop_func([&chan](int i) { chan.push(i); });
This can be disabled by adding the preprocesser definition COPPER_DISALLOW_MUTEX_RECURSION
to use the less secure but faster std::mutex
instead.
A complete collection of all public types and functions. Declarations are not necessarily the actual implementation in code. They only represent their behavior and their usage, while striving to be more readable as a reference compared to the actual lengthy definitions.
enum class channel_op_status { success, closed, unavailable };
enum class wait_type { forever, none, for_, until };
template<typename T, template<typename> typename... Args>
using buffered_channel = channel<true, T, Args...>;
template<typename T, template<typename> typename... Args>
using unbuffered_channel = channel<false, T, Args...>;
// non-void messages
template<bool is_buffered,
typename T,
template<typename...> typename BufferQueue = std::queue,
template<typename...> typename OpQueue = std::deque>
struct channel {
using value_type = T;
// Constructor, Assignment, Destructor
channel() = default;
explicit channel(size_t max_buffer_size);
channel(channel&& other) noexcept;
channel(const channel& other) = delete;
~channel();
channel& operator=(const channel&) = delete;
channel& operator=(channel&&) = delete;
// pop
template<wait_type wtype, typename... Args>
[[nodiscard]] std::optional<T> pop_wt(
Args&& ... args
);
[[nodiscard]] std::optional<T> pop();
[[nodiscard]] std::optional<T> try_pop();
template<typename Rep, typename Period>
[[nodiscard]] std::optional<T> try_pop_for(
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] std::optional<T> try_pop_until(
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// pop_func
[[nodiscard]] channel_op_status pop_func_wt(
const std::function<void(T&&)>&,
Args&& ... args
);
[[nodiscard]] channel_op_status pop_func(
const std::function<void(T&&)>&
);
[[nodiscard]] channel_op_status try_pop_func(
const std::function<void(T&&)>&
);
template<typename Rep, typename Period>
[[nodiscard]] channel_op_status try_pop_func_for(
const std::function<void(T&&)>&,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] channel_op_status try_pop_func_until(
const std::function<void(T&&)>&,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// push
template<wait_type wtype, typename... Args>
[[nodiscard]] bool push_wt(
T,
Args&& ... args
);
[[nodiscard]] bool push(
T
);
[[nodiscard]] bool try_push(
T
);
template<typename Rep, typename Period>
[[nodiscard]] std::optional<T> try_push_for(
T,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] std::optional<T> try_push_until(
T,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// push_func
[[nodiscard]] channel_op_status push_func_wt(
const std::function<T()>&,
Args&& ... args
);
[[nodiscard]] channel_op_status push_func(
const std::function<T()>&
);
[[nodiscard]] channel_op_status try_push_func(
const std::function<T()>&
);
template<typename Rep, typename Period>
[[nodiscard]] channel_op_status try_push_func_for(
const std::function<T()>&,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] channel_op_status try_push_func_until(
const std::function<T()>&,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// views & iterators
[[nodiscard]] channel_pop_iterator<channel> begin();
[[nodiscard]] channel_pop_iterator<channel> end();
[[nodiscard]] channel_push_iterator<channel> push_iterator();
[[nodiscard]] channel_read_view<channel> read_view();
[[nodiscard]] channel_write_view<channel> write_view();
// Other functons
size_t clear();
void close();
[[nodiscard]] bool is_read_closed();
[[nodiscard]] bool is_write_closed();
};
// void messages
template<bool is_buffered,
template<typename...> typename BufferQueue = std::queue,
template<typename...> typename OpQueue = std::deque>
struct channel<is_buffered, void, BufferQueue, OpQueue> {
using value_type = void;
// Constructor, Assignment, Destructor
channel() = default;
explicit channel(size_t max_buffer_size);
channel(channel&& other) noexcept;
channel(const channel& other) = delete;
~channel();
channel& operator=(const channel&) = delete;
channel& operator=(channel&&) = delete;
// pop
template<wait_type wtype, typename... Args>
[[nodiscard]] bool pop_wt(
Args&& ... args
);
[[nodiscard]] bool pop();
[[nodiscard]] bool try_pop();
template<typename Rep, typename Period>
[[nodiscard]] bool try_pop_for(
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] sbool try_pop_until(
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// pop_func
[[nodiscard]] channel_op_status pop_func_wt(
const std::function<void()>&,
Args&& ... args
);
[[nodiscard]] channel_op_status pop_func(
const std::function<void()>&
);
[[nodiscard]] channel_op_status try_pop_func(
const std::function<void()>&
);
template<typename Rep, typename Period>
[[nodiscard]] channel_op_status try_pop_func_for(
const std::function<void()>&,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] channel_op_status try_pop_func_until(
const std::function<void()>&,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// push
template<wait_type wtype, typename... Args>
[[nodiscard]] bool push_wt(
Args&& ... args
);
[[nodiscard]] bool push();
[[nodiscard]] bool try_push();
template<typename Rep, typename Period>
[[nodiscard]] std::optional<T> try_push_for(
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] std::optional<T> try_push_until(
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// push_func
[[nodiscard]] channel_op_status push_func_wt(
const std::function<void()>&,
Args&& ... args
);
[[nodiscard]] channel_op_status push_func(
const std::function<void()>&
);
[[nodiscard]] channel_op_status try_push_func(
const std::function<void()>&
);
template<typename Rep, typename Period>
[[nodiscard]] channel_op_status try_push_func_for(
const std::function<void()>&,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] channel_op_status try_push_func_until(
const std::function<void()>&,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// views & iterators
[[nodiscard]] channel_pop_iterator<channel> begin();
[[nodiscard]] channel_pop_iterator<channel> end();
[[nodiscard]] channel_push_iterator<channel> push_iterator();
[[nodiscard]] channel_read_view<channel> read_view();
[[nodiscard]] channel_write_view<channel> write_view();
// Other functons
size_t clear();
void close();
[[nodiscard]] bool is_read_closed();
[[nodiscard]] bool is_write_closed();
};
[[nodiscard]] auto operator>>(channel<is_buffered, T, ...>&, const std::function<void(T&&)>);
[[nodiscard]] auto operator<<(channel<is_buffered, T, ...>&, const std::function<T()>);
[[nodiscard]] auto operator>>(channel<is_buffered, void, ...>&, const std::function<void()>);
[[nodiscard]] auto operator<<(channel<is_buffered, void, ...>&, const std::function<void()>);
[[nodiscard]] auto operator>>(channel<is_buffered, T, ...>&, T&);
[[nodiscard]] auto operator<<(channel<is_buffered, T, ...>&, T);
// copper::_ or copper::voidval for an element of voidval_t.
[[nodiscard]] auto operator>>(channel<is_buffered, void, ...>&, voidval_t);
[[nodiscard]] auto operator<<(channel<is_buffered, void, ...>&, voidval_t);
template<typename... Ops>
[[nodiscard]] channel_op_status select(
Ops&& ... ops
);
template<typename... Ops>
[[nodiscard]] channel_op_status try_select(
Ops&& ... ops
);
template<typename Rep, typename Period, typename... Ops>
[[nodiscard]] channel_op_status try_select_for(
const std::chrono::duration<Rep, Period>& rel_time, Ops&& ... ops
);
template<typename Clock, typename Duration, typename... Ops>
[[nodiscard]] channel_op_status
try_select_until(
const std::chrono::time_point<Clock, Duration>& timeout_time, Ops&& ... ops
);
template<typename... Args>
[[nodiscard]] channel_op_status vselect(
Args&& ... args
);
template<typename... Args>
[[nodiscard]] channel_op_status try_vselect(
Args&& ... args
);
template<typename Rep, typename Period, typename... Args>
[[nodiscard]] channel_op_status try_vselect_for(
const std::chrono::duration<Rep, Period>& rel_time, Args&& ... args
);
template<typename Clock, typename Duration, typename... Args>
[[nodiscard]] channel_op_status
try_vselect_until(
const std::chrono::time_point<Clock, Duration>& timeout_time, Args&& ... args
);
template<typenameChannelT>
struct channel_read_view {
using value_type = typename ChannelT::value_type;
explicit channel_read_view(ChannelT& channel);
// pop
template<wait_type wtype>
[[nodiscard]] auto pop_wt(
Args&& ... args
);
[[nodiscard]] auto pop();
[[nodiscard]] auto try_pop();
template<typename Rep, typename Period>
[[nodiscard]] auto try_pop_for(
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] auto try_pop_until(
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// pop_func
template<wait_type wtype>
[[nodiscard]] auto pop_func_wt(
const std::function<void(value_type&&)>&
);
[[nodiscard]] auto pop_func(
const std::function<void(value_type&&)>&
);
[[nodiscard]] auto try_pop_func(
const std::function<void(value_type&&)>&
);
template<typename Rep, typename Period>
[[nodiscard]] auto try_pop_func_for(
const std::function<void(value_type&&)>&,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] auto try_pop_func_until(
const std::function<void(value_type&&)>&,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// other functions
[[nodiscard]] channel_pop_iterator<ChannelT> begin();
[[nodiscard]] channel_pop_iterator<ChannelT> end();
[[nodiscard]] channel_read_view<ChannelT> read_view();
}:
template<typename ChannelT>
struct channel_write_view {
using value_type = typename ChannelT::value_type;
explicit channel_write_view(ChannelT& channel) : _channel(channel) {}
// push
template<wait_type wtype, typename... Args>
[[nodiscard]] bool push_wt(
value_type,
Args&& ... args
);
[[nodiscard]] bool push(
value_type
);
[[nodiscard]] bool try_push(
value_type
);
template<typename Rep, typename Period>
[[nodiscard]] std::optional<value_type> try_push_for(
value_type,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] std::optional<value_type> try_push_until(
value_type,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// push_func
[[nodiscard]] channel_op_status push_func_wt(
const std::function<value_type()>&,
Args&& ... args
);
[[nodiscard]] channel_op_status push_func(
const std::function<value_type()>&
);
[[nodiscard]] channel_op_status try_push_func(
const std::function<value_type()>&
);
template<typename Rep, typename Period>
[[nodiscard]] channel_op_status try_push_func_for(
const std::function<value_type()>&,
const std::chrono::duration<Rep, Period>& rel_time
);
template<typename Clock, typename Duration>
[[nodiscard]] channel_op_status try_push_func_until(
const std::function<value_type()>&,
const std::chrono::time_point<Clock, Duration>& timeout_time
);
// Other functions
[[nodiscard]] channel_push_iterator<ChannelT> push_iterator();
[[nodiscard]] channel_write_view write_view();
void close();
size_t clear();
};
template<typename ChannelT>
struct channel_pop_iterator {
using value_type = typename ChannelT::value_type;
using reference = std::add_lvalue_reference_t<value_type>;
using pointer = value_type*;
using difference_type = std::ptrdiff_t;
using iterator_category = std::input_iterator_tag;
channel_pop_iterator();
explicit channel_pop_iterator(ChannelT& channel);
auto operator++(int)&;
channel_pop_iterator& operator++()&;
[[nodiscard]] reference operator*() const;
[[nodiscard]] pointer operator->() const;
[[nodiscard]] bool operator==(const channel_pop_iterator& other) const;
[[nodiscard]] bool operator!=(const channel_pop_iterator& other) const;
};
template<typename ChannelT>
struct channel_push_iterator {
using value_type = _pusher;
using reference = value_type&;
using pointer = void;
using difference_type = std::ptrdiff_t;
using iterator_category = std::output_iterator_tag;
channel_push_iterator();
explicit channel_push_iterator(ChannelT& channel);
channel_push_iterator operator++(int)&;
channel_push_iterator& operator++()&;
[[nodiscard]] _pusher operator*() const;
};