Results and non-blocking operations

Buffers passed as rvalues are moved into an operation and handed back by value; buffers passed as lvalues are borrowed and handed back by reference. Blocking operations with two buffers return them in a result; non-blocking operations return an iresult, which owns the request and keeps owned buffers out of reach until the operation has completed. See Non-blocking operations.

Blocking results

template<typename SBuf, typename RBuf>
struct result

The send and receive buffer of a blocking operation with both, e.g. sendrecv or alltoallv.

A buffer passed as an lvalue is borrowed: the member is a reference to it. A buffer passed as an rvalue was moved into the operation and is handed back here by value. Unpack it with a structured binding:

auto [_, recv] = kamping::v2::allgatherv(local, kamping::v2::auto_recv_v<int>(), comm);

Defined in header <kamping/v2/result.hpp>

Non-blocking results

template<typename Buf>
class iresult<Buf>

The pending result of a non-blocking operation with one buffer, e.g. isend or irecv.

A buffer passed as an rvalue is moved into the iresult, which owns it until the operation has completed: it is only accessible through wait() or a successful test(), so it cannot be touched while MPI may still use it. A buffer passed as an lvalue is borrowed; you must not access it before completion.

auto pending = kamping::v2::irecv(std::vector<int>{} | kamping::v2::views::resize, source, comm);
// ... other work ...
std::vector<int> data = pending.wait().underlying();

An iresult is move-only, and moving it keeps the buffer in place. It must be completed with wait() or a successful test(): destroying or assigning over an iresult whose operation is still active prints a diagnostic and calls std::terminate(), instead of releasing a buffer that MPI may still be using.

Defined in header <kamping/v2/iresult.hpp>

iresult &operator=(iresult &&other) noexcept

Takes over other’s operation and buffer. Terminates if this iresult’s operation is still active.

template<convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
decltype(auto) wait(Status &&status = MPI_STATUS_IGNORE)

Blocks until the operation completes, then returns the buffer: an owned buffer by value (the iresult is left empty), a borrowed buffer by reference.

Parameters:
  • status – Optional: filled with the status of the operation. Defaults to MPI_STATUS_IGNORE.

template<convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
auto test(Status &&status = MPI_STATUS_IGNORE)

Checks whether the operation has completed, without blocking.

For a borrowed buffer, returns whether the operation has completed. For an owned buffer, returns a std::optional holding the buffer if it has (the iresult is then left empty), and std::nullopt if it is still active, in which case you can test again or wait().

Parameters:
  • status – Optional: filled with the status of the operation if it has completed. Defaults to MPI_STATUS_IGNORE.

template<typename SBuf, typename RBuf>
class iresult<SBuf, RBuf>

The pending result of a non-blocking operation with a send and a receive buffer, e.g. isendrecv.

Like the one-buffer iresult: buffers passed as rvalues are owned until completion, buffers passed as lvalues are borrowed, and the operation must be completed with wait() or a successful test(). Both return the buffers as a result.

Defined in header <kamping/v2/iresult.hpp>

iresult &operator=(iresult &&other) noexcept

Takes over other’s operation and buffers. Terminates if this iresult’s operation is still active.

template<convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
result<SBuf, RBuf> wait(Status &&status = MPI_STATUS_IGNORE)

Blocks until the operation completes, then returns both buffers: owned buffers by value (the iresult is left empty), borrowed buffers by reference.

Parameters:
  • status – Optional: filled with the status of the operation. Defaults to MPI_STATUS_IGNORE.

template<convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
std::optional<result<SBuf, RBuf>> test(Status &&status = MPI_STATUS_IGNORE)

Checks whether the operation has completed, without blocking.

Parameters:
  • status – Optional: filled with the status of the operation if it has completed. Defaults to MPI_STATUS_IGNORE.

Returns:

Both buffers, as from wait(), if the operation has completed; std::nullopt if it is still active, in which case you can test again or wait().

Requests

class request

Owning, move-only wrapper around an MPI_Request.

Satisfies convertible_to_mpi_handle_ptr<MPI_Request>, so it can be passed directly to any non-blocking operation as its request argument:

kamping::v2::request r;
kamping::v2::isend(r, buf, dest, comm);
r.wait();

The request must be completed explicitly — with wait(), a successful test(), or the free completion functions — or handed off with release(); the library never completes a request implicitly. Destroying or move-assigning over a request that is still active is a hard error: it prints a diagnostic and calls std::terminate(), also during exception unwinding. Unlike iresult, request does not own the buffer: the caller must keep it alive until the request completes.

Ownership can be handed back to raw MPI code with release(), and moved into or out of a request_vector with request_vector::push_back(request&&) / request_vector::extract().

Defined in header <kamping/v2/request.hpp>

request() noexcept = default
(1)
explicit request(MPI_Request req) noexcept
(2)

(1) Constructs a null request (MPI_REQUEST_NULL).

(2) Takes over responsibility for completing req. The caller must not complete req itself afterwards.

request &operator=(request &&other) noexcept

Takes over other’s request. Terminates if this still holds an active request.

~request()

Terminates if the request is still active (see class documentation).

bool is_null() const noexcept
Returns:

true if this holds MPI_REQUEST_NULL (never started, completed, or released).

MPI_Request release() noexcept

Gives up responsibility for the request and returns it; this becomes MPI_REQUEST_NULL.

operator request_view() noexcept
Returns:

A non-owning view of this request.

template<convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
void wait(Status &&status = MPI_STATUS_IGNORE)

Shorthand for wait(*this, status).

template<convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
bool test(Status &&status = MPI_STATUS_IGNORE)

Shorthand for test(*this, status).

class request_view

Non-owning wrapper around an MPI_Request*.

Satisfies convertible_to_mpi_handle_ptr<MPI_Request> so it can be passed to any non-blocking operation as its request argument. Does not complete or free the request on destruction — use request or iresult for RAII.

wait() and test() are shorthands for the free functions wait() and test() and follow the same semantics as iresult: wait() blocks until the operation completes; test() polls and returns true when done. Both accept an optional status out-parameter via convertible_to_mpi_handle_ptr<MPI_Status> (raw MPI_Status*, status_view, or omitted to use MPI_STATUS_IGNORE). Since request_view is non-owning, wait() returns void — the buffer is the caller’s responsibility.

Defined in header <kamping/v2/request.hpp>

class request_vector

Owning, growable array of MPI requests that mirrors std::vector, but whose element access yields request_views instead of raw MPI_Requests.

The requests are stored contiguously, so the whole array can be passed to the multiple-completion functions (waitall, testany, waitsome, …) without copying; it exposes them via mpi_handle_range() rather than data(), since its elements as a range are request_views, not MPI_Requests.

Typical use is to hand a fresh slot straight to a non-blocking operation:

kamping::v2::request_vector requests;
kamping::v2::isend(requests.emplace_back(), buf, dest, comm);
kamping::v2::waitall(requests);

Ownership: move-only. Like request, the vector never completes a request implicitly: every request must be completed explicitly (e.g. waitall(requests)) or handed off with release()/extract() before it is dropped. Dropping an active request prints a diagnostic and calls std::terminate(). This applies to the destructor and move assignment, and equally to the modifiers that remove slots: clear(), shrinking resize() and pop_back().

Invalidation follows std::vector: a request_view points into the internal storage, so it is invalidated by any reallocation (emplace_back/push_back beyond capacity(), reserve, resize) and by erase_null(). The MPI_Request handles themselves are plain values and survive reallocation unchanged.

There is no const element access: a request_view must be able to complete (and thus write) its request.

Defined in header <kamping/v2/request.hpp>

explicit request_vector(size_type n)

Constructs n slots, each MPI_REQUEST_NULL.

request_vector &operator=(request_vector &&other) noexcept

Takes over the requests of other. Terminates if this still holds an active request.

~request_vector()

Terminates if any request is still active (see class documentation).

void reserve(size_type n)

Reserves storage for n requests. Invalidates all views if it reallocates.

void resize(size_type n)

Resizes to n slots. New slots are MPI_REQUEST_NULL.

Terminates if a dropped slot (index >= n) still holds an active request; complete those first.

void clear()

Removes all slots. Terminates if any request is still active; complete them first (e.g. waitall).

request_view emplace_back()

Appends a MPI_REQUEST_NULL slot and returns a view of it, ready to be passed to a non-blocking operation as its request output parameter.

void push_back(MPI_Request req)
(1)
void push_back(request &&req)
(2)

(1) Appends the raw handle req, taking over responsibility for completing it.

The caller must not complete req itself afterwards: once the vector completes it, MPI frees it and any remaining copy of the handle is stale.

(2) Moves the owning request req into the vector; req is left null.

MPI_Request release(size_type i) noexcept

Gives up responsibility for the request at index i and returns it as a raw handle.

The slot becomes MPI_REQUEST_NULL but is not removed, so other indices and views stay valid; remove empty slots later with erase_null(). The caller must complete the returned request. Precondition: i < size().

request extract(size_type i) noexcept

Moves the request at index i out into an owning request.

Like release(i), the slot becomes MPI_REQUEST_NULL and is not removed. Precondition: i < size().

void pop_back()

Removes the last slot. Terminates if it still holds an active request. Precondition: !empty().

size_type erase_null()

Removes all MPI_REQUEST_NULL slots (completed or never started), keeping the order of the rest.

Useful in polling loops after testsome/waitsome. Inactive persistent requests are not MPI_REQUEST_NULL and are kept. Invalidates all views.

Returns:

The number of removed slots.

request_view operator[](size_type i) noexcept
Returns:

A view of the request at index i. Precondition: i < size().

Completion

The functions completing several requests take any writable contiguous range of MPI_Requests, such as a request_vector or a std::vector<MPI_Request>, and of MPI_Statuses for the statuses:

template<typename T>
concept request_range
template<typename T>
concept request_range = convertible_to_mpi_handle_range<T, MPI_Request>
                        && std::ranges::sized_range<decltype(kamping::v2::handle_range(std::declval<T&>()))>

The request array of a multi-request call (waitall, waitany, waitsome, …): a writable contiguous range of MPI_Request obtained through handle_range(). It must be sized, since its size is the request count.

Defined in header <mpi/arrays.hpp>

template<typename T, typename E>
concept output_array_of
template<typename T, typename E>
concept output_array_of =
    (builtin_handle<E> && convertible_to_mpi_handle_range<T, E>) || (!builtin_handle<E> && output_range_of<T&, E>)

An output array of E. Handle elements are obtained through handle_range(), so that types such as status_vector can expose their storage; other element types (e.g. int indices) must be a plain contiguous range. A sized array is checked to hold the required number of elements; an unsized one is trusted.

Defined in header <mpi/arrays.hpp>

template<typename T, typename E>
concept output_array_or_ignore_of
template<typename T, typename E>
concept output_array_or_ignore_of = output_array_of<T, E> || output_array_sentinel_of<T, E>

An output array of E that MPI allows to be ignored: an output_array_of<E> or a matching sentinel.

Defined in header <mpi/arrays.hpp>

template<convertible_to_mpi_handle_ptr<MPI_Request> Request, convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
void wait(Request &&request, Status &&status = MPI_STATUS_IGNORE)

Blocks until the operation associated with request completes (MPI_Wait).

On return, request is set to MPI_REQUEST_NULL (or marked inactive, for persistent requests). Waiting on MPI_REQUEST_NULL returns immediately with an empty status.

Parameters:
  • request – Anything convertible to an MPI_Request* (raw MPI_Request, request_view, …).

  • status – Optional status out-parameter; omit to use MPI_STATUS_IGNORE.

Defined in header <kamping/v2/completion.hpp>

template<convertible_to_mpi_handle_ptr<MPI_Request> Request, convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
bool test(Request &&request, Status &&status = MPI_STATUS_IGNORE)

Checks whether the operation associated with request has completed (MPI_Test).

If it has, request is set to MPI_REQUEST_NULL (or marked inactive, for persistent requests) and status is filled in. Testing MPI_REQUEST_NULL returns true.

Parameters:
  • request – Anything convertible to an MPI_Request* (raw MPI_Request, request_view, …).

  • status – Optional status out-parameter; omit to use MPI_STATUS_IGNORE.

Returns:

true if the operation has completed.

Defined in header <kamping/v2/completion.hpp>

template<request_range Requests, output_array_or_ignore_of<MPI_Status> Statuses = statuses_ignore_t>
void waitall(Requests &&requests, Statuses &&statuses = statuses_ignore_t{})

Blocks until all operations in requests complete (MPI_Waitall).

On return, every request is set to MPI_REQUEST_NULL (or marked inactive, for persistent requests). MPI_REQUEST_NULL entries are allowed and ignored.

Parameters:
  • requests – A writable contiguous range of MPI_Request (see request_range).

  • statuses – Optional: a writable contiguous range of MPI_Status with at least as many entries as requests; statuses[i] receives the status of requests[i]. Defaults to statuses_ignore.

Defined in header <kamping/v2/completion.hpp>

template<request_range Requests, output_array_or_ignore_of<MPI_Status> Statuses = statuses_ignore_t>
bool testall(Requests &&requests, Statuses &&statuses = statuses_ignore_t{})

Checks whether all operations in requests have completed (MPI_Testall).

Either all requests complete and are set to MPI_REQUEST_NULL, or none of them is modified.

Parameters:
  • requests – A writable contiguous range of MPI_Request (see request_range).

  • statuses – Optional: a writable contiguous range of MPI_Status with at least as many entries as requests; only written if this returns true. Defaults to statuses_ignore.

Returns:

true if all operations have completed.

Defined in header <kamping/v2/completion.hpp>

template<request_range Requests, convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
std::optional<std::size_t> waitany(Requests &&requests, Status &&status = MPI_STATUS_IGNORE)

Blocks until any one operation in requests completes (MPI_Waitany).

The completed request is set to MPI_REQUEST_NULL (or marked inactive, for persistent requests).

Parameters:
  • requests – A writable contiguous range of MPI_Request (see request_range).

  • status – Optional status out-parameter for the completed operation; omit to use MPI_STATUS_IGNORE.

Returns:

The index of the completed request, or std::nullopt if requests contains no active request (MPI_UNDEFINED).

Defined in header <kamping/v2/completion.hpp>

template<request_range Requests, convertible_to_mpi_handle_ptr<MPI_Status> Status = MPI_Status*>
testany_result testany(Requests &&requests, Status &&status = MPI_STATUS_IGNORE)

Checks whether any one operation in requests has completed (MPI_Testany).

Parameters:
  • requests – A writable contiguous range of MPI_Request (see request_range).

  • status – Optional status out-parameter for the completed operation; omit to use MPI_STATUS_IGNORE.

Returns:

A testany_result: pending, the completed index, or no active request.

Defined in header <kamping/v2/completion.hpp>

class testany_result

Result of testany: an optional-like index with an additional pending state.

Exactly one of three states holds:

  • pending(): no operation has completed yet; waitany would block.

  • has_value(), index *r: requests[*r] completed and was set to MPI_REQUEST_NULL.

  • neither: requests contains no active request (MPI_UNDEFINED).

There is deliberately no operator bool: if (r) could plausibly mean either “not pending” or “has an index”, and guessing wrong in a polling loop spins forever. Call pending() or has_value() explicitly.

Defined in header <kamping/v2/completion.hpp>

testany_result(bool flag, int index) noexcept

Constructs from the flag and index outputs of MPI_Testany.

bool pending() const noexcept
Returns:

true if no operation has completed yet.

bool has_value() const noexcept
Returns:

true if an operation completed; its index is then available via *r or value().

std::size_t operator*() const noexcept
Returns:

The index of the completed request. Precondition: has_value().

std::size_t value() const
Throws:
  • std::bad_optional_access – if !has_value().

Returns:

The index of the completed request.

std::optional<std::size_t> index() const noexcept
Returns:

What waitany would have returned: the completed index, or std::nullopt if there was no active request. Precondition: !pending().

template<request_range Requests, output_array_of<int> Indices, output_array_or_ignore_of<MPI_Status> Statuses = statuses_ignore_t>
std::optional<std::size_t> waitsome(Requests &&requests, Indices &&indices, Statuses &&statuses = statuses_ignore_t{})

Blocks until at least one operation in requests completes (MPI_Waitsome).

Completed requests are set to MPI_REQUEST_NULL (or marked inactive, for persistent requests).

Parameters:
  • requests – A writable contiguous range of MPI_Request (see request_range).

  • indices – A writable contiguous range of int with at least as many entries as requests; the first n entries receive the indices of the completed requests.

  • statuses – Optional: a writable contiguous range of MPI_Status with at least as many entries as requests, written in completion order: statuses[k] belongs to requests[indices[k]], not to requests[k]. Defaults to statuses_ignore.

Returns:

The number n of completed requests, or std::nullopt if requests contains no active request (MPI_UNDEFINED).

Defined in header <kamping/v2/completion.hpp>

template<request_range Requests, output_array_of<int> Indices, output_array_or_ignore_of<MPI_Status> Statuses = statuses_ignore_t>
std::optional<std::size_t> testsome(Requests &&requests, Indices &&indices, Statuses &&statuses = statuses_ignore_t{})

Checks which operations in requests have completed, without blocking (MPI_Testsome).

Same contract as waitsome, except that it returns 0 if no operation has completed yet.

Returns:

The number of completed requests (possibly 0), or std::nullopt if requests contains no active request (MPI_UNDEFINED).

Defined in header <kamping/v2/completion.hpp>

statuses_ignore_t statuses_ignore

The statuses_ignore_t sentinel.

Defined in header <kamping/v2/sentinels.hpp>