Tutorial » Creating continuables

Explains how to create a continuable_base.

A continuable is an arbitrary instantiation of a continuable_base, it represents the main class of the library and makes it possible to build up an asynchronous call hierarchy. When dealing with continuables we usually don't know its exact type for avoiding expensive type erasure.

The continuable_base is convertible to a continuable which represents a specified type of the continuable_base on the cost of a type erasure.

From a value or exception

The library provides make_ready_continuable which may be used to create a continuable_base from an arbitrary amount of values:

auto one = cti::make_ready_continuable(0);

cti::continuable<int, float, char> three =
  cti::make_ready_continuable(0, 1.f, '2');

Additionally a continuable_base which resolves with an exception may be created through make_exceptional_continuable.

cti::continuable<int> c = cti::make_exceptional_continuable(std::exception{});

From a promise taking callable

The main function for creating a continuable_base is make_continuable which must be invoked with the types of arguments it resolves to. It accepts a callable object which accepts an arbitrary object (the promise_base). The promise_base is created by the library and then passed to the given callback. This is in contrast to the usage of the standard std::promise which is created by the user.

The promise_base exposes methods to resolve it through result values or through an exception. Below we implement pseudo http_request function which resolves the request asynchronously trough a std::string.

auto http_request(std::string url) {
  return cti::make_continuable<std::string>(
    [url = std::move(url)](auto&& promise) {
      // Resolve the promise upon completion of the task.
      promise.set_value("<html> ... </html>");

      // Or promise.set_exception(...);
    });
}

An alternative would be a continuable_base with a result of zero arguments:

auto wait_for(std::chrono::milliseconds duration) {
  return cti::make_continuable<void>([](auto&& promise) {
  //                           ^^^^

  // Resolve the promise later when the duration is over
  promise.set_value();
});

A continuable_base may resolve with an arbitrary count of result values:

auto resolve_sth() {
  return cti::make_continuable<int, int, float, char>(
    [](auto&& promise) {
      promise.set_value(0, 1, 2.f, '3');
    });

A promise_base always exposes a call operator for resolving it as like when using promise_base::set_value or promise_base::set_exception. See promise_base for details.

The continuable invocation model

An asynchronous call hierarchy that is stored inside the continuable_base is executed when its result is requested (lazy evaluation) in contrast to other commonly used implementations such as std::future which execute the asynchronous call hierarchy instantly on creation (eager evaluation).

The lazy evaluation strategy used by continuables has many benefits over eager evaluation that is used by other common implementations:

  • prevention of side effects
  • evasion of race conditions
  • ensured deterministic behaviour.

The asynchronous call hierarchy is started when the continuable_base is destructed or the continuable_base::done method is called. It is possible to disable the automatic start through calling continuable_base::freeze on the corresponding continuable_base.