Skip to content

Disjunctions

The function when_any is defined for task disjunctions. when_any returns a when_any_future that is able to aggregate different futures types and become ready when any of the internal futures is ready.

Say we want to execute the following sequence of asynchronous tasks where the first future between B and C to get ready should be used as input for the continuation D.

graph LR subgraph Async A --> B A --> C B --> |set if first|D C --> |set if first|D end Main --> A D --> End Main --> End

Achieving that with the function when_all is as simple as:

shared_cfuture<int> A = async([]() { return 2; }).share();

cfuture<int> B = then(A, [](int a) { return a * 2; });
cfuture<int> C = then(A, [](int a) { return a * 3; });

cfuture<int> D = then(when_any(B, C), [](int b_or_c) {
    return b_or_c * 3;
});

int r = D.get();
assert(r == 12 || r == 18);

The when_any_future object acts as a proxy that checks the state of each internal future. If none of the internal futures is ready yet, is_ready returns false.

cfuture<int> f1 = async([]() { return 2; });
cfuture<double> f2 = async([]() { return 3.5; });
cfuture<std::string> f3 = async([]() -> std::string {
    return "name";
});
auto f = when_any(f1, f2, f3); // or f1 || f2 || f3

An instance of when_any_future returns a when_any_result which keeps the sequence of futures and the index of the first future to get ready.

when_any_result any_r = f.get();
size_t i = any_r.index;
auto [r1, r2, r3] = std::move(any_r.tasks);
if (i == 0) {
    assert(r1.get() == 2);
} else if (i == 1) {
    assert(r2.get() == 3.5);
} else {
    assert(r3.get() == "name");
}

The underlying sequence might be a tuple or a range.

Operator

The operator || is defined as a convenience to create future disjunctions in large task graphs.

cfuture<int> f1 = async([]() { return 2; });
cfuture<int> f2 = async([]() { return 3; });
cfuture<int> f3 = async([]() { return 4; });
auto any = f1 || f2 || f3;

With when_any_result unwrapping, this becomes a powerful tool to manage continuations:

auto f4 = any >> [](int first) { return first; };
int r = f4.get();
assert(r >= 2 && r <= 4);

Note that the operator && uses expression templates to create a single disjunction of futures. Thus, f1 || f2 || f3 is equivalent to when_any(f1, f2, f3) rather than when_any(when_any(f1, f2), f3).

The operator || can also be used with lambdas as an easy way to launch new tasks already into disjunctions:

auto f1 = []() { return 2; } ||
          []() { return 3; } ||
          []() { return 4; };
auto f2 = then(f1, [](int first) {
    assert(first >= 2 && first <= 4);
});

This allows us to assemble task graphs where lambdas are treated as other first class types. The types accepted by these operators are limited to those matching the future concept and callables that are valid as new tasks.

Lazy continuations

Unlike when_all_future, the behavior of when_any_future is strongly affected by the features of its internal futures. Internal futures that support lazy continuations and external notifiers can only set a flag indicating to when_any_future that one of its futures is ready.

For internal futures that do not support lazy continuations, when_any_future needs to pool its internal future to check if any is ready, much like wait_for_any.

Disjunction unwrapping

Special unwrapping functions are defined for when_any_future continuations.

cfuture<int> f1 = async([]() { return 1; });
cfuture<int> f2 = async([]() { return 2; });
when_any_future<std::tuple<cfuture<int>, cfuture<int>>>
    f3 = when_any(f1, f2);
auto f4 = f3 >>
          [](std::size_t idx,
             std::tuple<cfuture<int>, cfuture<int>> prev) {
    if (idx == 0) {
        return std::get<0>(prev).get();
    } else {
        return std::get<1>(prev).get();
    }
};
int r = f4.get();
assert(r == 1 || r == 2);
auto f1 = async([]() { return 1; });
auto f2 = async([]() { return 2; });
auto f3 = when_any(f1, f2);
auto f4 = f3 >>
          [](std::size_t idx, cfuture<int> f1, cfuture<int> f2) {
    if (idx == 0) {
        return f1.get();
    } else {
        return f2.get();
    }
};
int r = f4.get();
assert(r >= 1 && r <= 2);
auto f1 = async([]() { return 1; });
auto f2 = async([]() { return 2; });
auto f3 = when_any(f1, f2);
auto f4 = f3 >> [](cfuture<int> f) {
    return f.get();
};
int r = f4.get();
assert(r >= 1 && r <= 2);
auto f1 = async([]() { return 1; });
auto f2 = async([]() { return 2; });
auto f3 = when_any(f1, f2);
auto f4 = f3 >> [](int v) {
    return v * 2;
};
int r = f4.get();
assert(r == 2 || r == 4);