Skip to content

Commit

Permalink
Merge pull request #1355 from msimberg/any-sender-documentation
Browse files Browse the repository at this point in the history
Add documentation for `any_sender` and `unique_any_sender`
  • Loading branch information
msimberg authored Dec 9, 2024
2 parents 152cb75 + d42f2f2 commit fc12960
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 2 deletions.
10 changes: 8 additions & 2 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ INPUT = "$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/async_cuda"
"$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/async_cuda_base" \
"$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/init_runtime" \
"$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/runtime" \
"$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/execution"
"$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/execution" \
"$(PIKA_DOCS_DOXYGEN_INPUT_ROOT)/libs/pika/execution_base"
FILE_PATTERNS = *.cpp *.hpp *.cu
RECURSIVE = YES
EXCLUDE_PATTERNS = */test */detail
Expand All @@ -20,4 +21,9 @@ EXTRACT_ALL = YES
ENABLE_PREPPROCESSING = YES
MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = YES
PREDEFINED = PIKA_EXPORT= PIKA_NVCC_PRAGMA_HD_WARNING_DISABLE= "PIKA_STATIC_CALL_OPERATOR(...)=operator()(__VA_ARGS__) const" PIKA_FORCEINLINE=
PREDEFINED = PIKA_EXPORT= \
PIKA_FORCEINLINE= \
PIKA_HAVE_CXX20_TRIVIAL_VIRTUAL_DESTRUCTOR= \
PIKA_NVCC_PRAGMA_HD_WARNING_DISABLE= \
"PIKA_STATIC_CALL_OPERATOR(...)=operator()(__VA_ARGS__) const" \
PIKA_STDEXEC_SENDER_CONCEPT=
9 changes: 9 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ All sender adaptors are `customization point objects (CPOs)
:language: c++
:start-at: #include

.. doxygenclass:: pika::execution::experimental::unique_any_sender
.. doxygenclass:: pika::execution::experimental::any_sender
.. doxygenfunction:: pika::execution::experimental::make_unique_any_sender
.. doxygenfunction:: pika::execution::experimental::make_any_sender

.. literalinclude:: ../examples/documentation/any_sender_documentation.cpp
:language: c++
:start-at: #include

.. _header_pika_cuda:

CUDA/HIP support (``pika/cuda.hpp``)
Expand Down
1 change: 1 addition & 0 deletions examples/documentation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

set(example_programs
any_sender_documentation
drop_operation_state_documentation
drop_value_documentation
hello_world_documentation
Expand Down
77 changes: 77 additions & 0 deletions examples/documentation/any_sender_documentation.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) 2024 ETH Zurich
//
// SPDX-License-Identifier: BSL-1.0
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include <pika/execution.hpp>
#include <pika/init.hpp>

#include <fmt/printf.h>

#include <chrono>
#include <cstddef>
#include <string_view>
#include <thread>
#include <utility>

void print_answer(std::string_view message,
pika::execution::experimental::unique_any_sender<int>&& sender)
{
auto const answer =
pika::this_thread::experimental::sync_wait(std::move(sender));
fmt::print("{}: {}\n", message, answer);
}

int main(int argc, char* argv[])
{
namespace ex = pika::execution::experimental;
namespace tt = pika::this_thread::experimental;

pika::start(argc, argv);
ex::thread_pool_scheduler sched{};

ex::unique_any_sender<int> sender;

// Whether the sender is a simple just-sender...
sender = ex::just(42);
print_answer("Quick answer", std::move(sender));

// ... or a more complicated sender, we can put them both into the same
// unique_any_sender as long as they send the same types.
sender = ex::schedule(sched) | ex::then([]() {
std::this_thread::sleep_for(std::chrono::seconds(3));
return 42;
});
print_answer("Slow answer", std::move(sender));

// If we try to use the sender again it will throw an exception
try
{
// NOLINTNEXTLINE(bugprone-use-after-move)
tt::sync_wait(std::move(sender));
}
catch (std::exception const& e)
{
fmt::print("Caught exception: {}\n", e.what());
}

// We can also use a type-erased sender to chain work. The type of the
// sender remains the same each iteration thanks to the type-erasure, but
// the work it represents grows.
//
// However, note that using a specialized algorithm like repeat_n from
// stdexec may be more efficient.
ex::unique_any_sender<int> chain{ex::just(0)};
for (std::size_t i = 0; i < 42; ++i)
{
chain = std::move(chain) | ex::continues_on(sched) |
ex::then([](int x) { return x + 1; });
}
print_answer("Final answer", std::move(chain));

pika::finalize();
pika::stop();

return 0;
}
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,23 @@ namespace pika::execution::experimental {
template <typename... Ts>
class any_sender;

/// \brief Type-erased move-only sender.
///
/// This class wraps senders that send types \p Ts in the value channel. This wrapper class does
/// not support arbitrary completion signatures, but requires a single value and error
/// completion signature. The value completion signature must send types \p Ts. The error
/// completion must send a \p std::exception_ptr. The wrapped sender may have a stopped
/// completion signature.
///
/// The \ref unique_any_sender requires senders that are move-constructible and connectable with
/// r-value references to the sender. The \ref unique_any_sender itself must also be connected
/// with an r-value reference (i.e. moved when passing into sender adaptors or consumers).
///
/// Sending references in the completion signature is not supported.
///
/// An empty \ref unique_any_sender throws when connected to a receiver.
///
/// \tparam Ts types sent in the value channel.
template <typename... Ts>
class unique_any_sender
#if !defined(PIKA_HAVE_CXX20_TRIVIAL_VIRTUAL_DESTRUCTOR)
Expand All @@ -727,15 +744,19 @@ namespace pika::execution::experimental {

public:
PIKA_STDEXEC_SENDER_CONCEPT

/// \brief Default-construct an empty \ref unique_any_sender.
unique_any_sender() = default;

/// \brief Construct a \ref unique_any_sender containing \p sender.
template <typename Sender,
typename = std::enable_if_t<!std::is_same_v<std::decay_t<Sender>, unique_any_sender>>>
unique_any_sender(Sender&& sender)
{
storage.template store<impl_type<Sender>>(std::forward<Sender>(sender));
}

/// \brief Assign \p sender to the \ref unique_any_sender.
template <typename Sender,
typename = std::enable_if_t<!std::is_same_v<std::decay_t<Sender>, unique_any_sender>>>
unique_any_sender& operator=(Sender&& sender)
Expand All @@ -750,13 +771,15 @@ namespace pika::execution::experimental {
unique_any_sender& operator=(unique_any_sender&&) = default;
unique_any_sender& operator=(unique_any_sender const&) = delete;

/// \brief Construct a \ref unique_any_sender from an \ref any_sender.
// cppcheck-suppress noExplicitConstructor
unique_any_sender(any_sender<Ts...>&& other)
: storage(std::move(other.storage))
{
other.reset();
}

/// \brief Assign a \ref any_sender to a \ref unique_any_sender.
unique_any_sender& operator=(any_sender<Ts...>&& other)
{
storage = std::move(other.storage);
Expand Down Expand Up @@ -800,6 +823,7 @@ namespace pika::execution::experimental {
PIKA_UNREACHABLE;
}

/// \brief Assign \p sender to the \ref unique_any_sender.
template <typename Sender>
void reset(Sender&& sender)
{
Expand All @@ -810,13 +834,31 @@ namespace pika::execution::experimental {
else { storage.template store<impl_type<Sender>>(std::forward<Sender>(sender)); }
}

/// \brief Empty the \ref unique_any_sender.
void reset() { storage.reset(); }

/// \brief Check if the \ref unique_any_sender is empty.
///
/// \return True if the \ref unique_any_sender is empty, i.e. default-constructed or
/// moved-from.
bool empty() const noexcept { return storage.empty(); }

/// \brief Check if the \ref unique_any_sender is non-empty.
///
/// See \ref empty().
explicit operator bool() const noexcept { return !empty(); }
};

/// \brief Type-erased copyable sender.
///
/// See \ref unique_any_sender for an overview. Compared to \ref unique_any_sender, the \ref
/// any_sender requires the wrapped senders to be l-value reference connectable and copyable.
/// The \ref any_sender itself is also l-value reference connectable and copyable. Otherwise it
/// behaves the same as \ref unique_any_sender.
///
/// A \ref unique_any_sender can be constructed from a \ref any_sender, but not vice-versa.
///
/// \tparam Ts types sent in the value channel.
template <typename... Ts>
class any_sender
#if !defined(PIKA_HAVE_CXX20_TRIVIAL_VIRTUAL_DESTRUCTOR)
Expand All @@ -836,8 +878,11 @@ namespace pika::execution::experimental {

public:
PIKA_STDEXEC_SENDER_CONCEPT

/// \brief Default-construct an empty \ref any_sender.
any_sender() = default;

/// \brief Construct a \ref any_sender containing \p sender.
template <typename Sender,
typename = std::enable_if_t<!std::is_same_v<std::decay_t<Sender>, any_sender>>>
any_sender(Sender&& sender)
Expand All @@ -849,6 +894,7 @@ namespace pika::execution::experimental {
storage.template store<impl_type<Sender>>(std::forward<Sender>(sender));
}

/// \brief Assign \p sender to the \ref any_sender.
template <typename Sender,
typename = std::enable_if_t<!std::is_same_v<std::decay_t<Sender>, any_sender>>>
any_sender& operator=(Sender&& sender)
Expand Down Expand Up @@ -899,6 +945,7 @@ namespace pika::execution::experimental {
return {std::move(moved_storage.get()), std::forward<Receiver>(receiver)};
}

/// \brief Assign \p sender to the \ref any_sender.
template <typename Sender>
void reset(Sender&& sender)
{
Expand All @@ -916,10 +963,18 @@ namespace pika::execution::experimental {
}
}

/// \brief Empty the \ref any_sender.
void reset() { storage.reset(); }

/// \brief Check if the \ref any_sender is empty.
///
/// \return True if the \ref any_sender is empty, i.e. default-constructed or
/// moved-from.
bool empty() const noexcept { return storage.empty(); }

/// \brief Check if the \ref any_sender is non-empty.
///
/// See \ref empty().
explicit operator bool() const noexcept { return !empty(); }
};

Expand Down Expand Up @@ -948,12 +1003,20 @@ namespace pika::execution::experimental {
}
} // namespace detail

/// \brief Helper function to construct a \ref unique_any_sender.
///
/// The template parameters for \ref unique_any_sender are inferred from the value types sent by
/// the given sender \p sender.
template <typename Sender, typename = std::enable_if_t<is_sender_v<Sender>>>
auto make_unique_any_sender(Sender&& sender)
{
return detail::make_any_sender_impl<unique_any_sender>(std::forward<Sender>(sender));
}

/// \brief Helper function to construct a \ref any_sender.
///
/// The template parameters for \ref any_sender are inferred from the value types
/// sent by the given sender \p sender.
template <typename Sender, typename = std::enable_if_t<is_sender_v<Sender>>>
auto make_any_sender(Sender&& sender)
{
Expand Down

0 comments on commit fc12960

Please sign in to comment.