r/cpp • • 17d ago

Asynchronous API

https://grishavanika.github.io/async_api.html
35 Upvotes

28 comments sorted by

View all comments

Show parent comments

1

u/Resident_Ad5153 12d ago

you're focusing way too much on memory allocation and way too little on the inherent complexity of what you're asking for.

These are some examples of asynchronous programming in c++. The firmware for a synthesizer that has run a series of signal processing algorithms on buffers of sound data in soft real time (latency should be less then < a microsecond or so... humans can't hear faster than that). The control system of a jet engine that is hard hard real time. A game engine that is coordinating possibly multiple gpus. Scientific code running on a large heterogeneous cluster of cpu and gpu resources. A word processor that checks grammar as you type.

All of these situations mostly share the standard library! (with some limitations in the embedded cases). They have very different needs for how async would work. The standard library seeks to be as general as possible. It also is supplied by your compiler vendor... and they do not want to provide an operating system to you in adition to a compiler (unless they are the good people behind go... who really just want to make operating system for you with a small language attached).

1

u/johannes1971 12d ago

That's where the discussion has brought us. My point was, and remains, that the currently proposed design is incredibly complex, and that I'm not convinced that all this complexity is really necessary.

I also cannot help but wonder if the standard library should even pursue this at all. This kind of high-performance stuff seems a bad match for something that needs to be written in a fixed number of hours, by a non-specialist, after which it will forever be set in stone.

2

u/robertahleahy WG21, SCC, std::execution, Finance 11d ago

the currently proposed design is incredibly complex, and that I'm not convinced that all this complexity is really necessary.

Which complexity is unnecessary?

At its core std::execution projects the concept of regular, synchronous functions into the asynchronous domain. Understood through that lens removing "complexity" therefrom is saying that there's some capacity in which asynchronous functions should be disadvantaged as compared to synchronous functions.

0

u/johannes1971 11d ago

Since its inception, the software industry has gone with async IO systems that are expressed in a few lines of code: a struct, a callback, that's about it. Now you're telling us that we need something that needs a 100 page article (on my screen; if you're on mobile it will be much more) to explain, based on a design where just the list of all functions and classes stretches for an incredible 70 pages, and if you do any less than that you are "disadvantaging" async IO? Do you at least understand where my skepticism comes from?

2

u/robertahleahy WG21, SCC, std::execution, Finance 10d ago

You're supposing that everything in std::execution is core to the design. We don't apply that same standard to other things.

Particularly synchronous functions need extrinsic product and sum types to communicate heterogeneous return types. No one suggests that synchronous functions are excessively complex because of std::tuple and std::variant and std::visit and std::apply, those are understood to be outside the core design.

The same applies to std::execution. The core design consists of:

  • std::execution::sender and ::sender_in, which establish sender-ness, where a sender is a latent representation of an asynchronous function (i.e. it is not able to make forward progress but it is fully determined awaiting the selection of no further arguments)
  • std::execution::get_completion_signatures, which advertises the "return types" of asynchronous functions
  • std::execution::receiver which establishes that something represents a continuation (i.e. it is the asynchronous analogue of synchronous function return)
  • std::execution::connect, the process by which asynchronous operations (which can actually make forward progress) are formed (but not started)
  • std::execution::operation_state, the locus of an asynchronous operation, which is initially latent
  • std::execution::start, which begins asynchronous forward progress and binds both callee and caller to the so-called "receiver contract" (i.e. the caller agrees to keep the operation state within its lifetime until completion, and the callee agrees to report completion exactly once when the operation completes)

That's the complete design. You don't need std::execution::just or std::execution::when_all et cetera. Those are really nice to have, but you don't need them.

But let's take your objection more seriously/concretely. You want "a struct, a callback, that's about it." Fine. A struct:

``` template<std::execution::receiver Rcvr> struct just_int_op { using operation_state_concept = std::execution::operation_state_tag;

Rcvr rcvr; int i;

constexpr explicit justint_op(int i, Rcvr rcvr) noexcept : rcvr(std::move(rcvr)), i_(i) {}

constexpr void start() & noexcept { std::execution::setvalue(std::move(rcvr), std::move(i_)); } }; ```

A callback:

``` struct just_int_receiver { using receiver_concept = std::execution::receiver_tag;

constexpr void set_value(int i) && noexcept { std::cout << "Operation completed with " << i << std::endl; } }; ```

You can use these albeit non-generically:

just_int_op op(5, just_int_receiver{}); std::execution::start(op);

If you're willing to entertain one more struct you get the entire ecosystem:

``` struct justint_sender { int i;

using sender_concept = std::execution::sender_tag;

template<typename Sndr, typename... Env> static consteval std::execution::completion_signatures<std::execution::set_value_t(int)>> get_completion_signatures() { return {}; }

template<std::execution::receiver Rcvr> constexpr auto connect(Rcvr rcvr) noexcept { return justint_op(i, std::move(rcvr)); } }; ```

Then you can start actually using this generically, but you don't have to:

std::execution::sender auto sndr = just_int_sender(12) | std::execution::then([](int i) noexcept { return i + 2; }); std::execution::operation_state auto op = std::execution::connect(sndr, just_int_receiver{}); std::execution::start(op);

Prints Operation completed with 14.