Customization points

KaMPIng-v2 finds the MPI properties of a buffer, and the native handle of an MPI object, through a small set of accessor functions. Each one is looked up in three steps: a traits specialization, for types you don’t own; a member function, for types you control; and a fallback for standard ranges and native MPI handles. See Data buffers and MPI objects.

The traits and concepts are defined in mpi-core, in mpi::experimental, and re-exported in kamping::v2. Specialize the traits in mpi::experimental.

Buffers

template<typename T>
struct buffer_traits

Trait class for non-intrusive MPI buffer customization (see the file documentation for an example). Only implement the members you need (count, ptr, type, counts, displs); the remaining accessors fall back to the mpi_count() / mpi_ptr() / mpi_type() / mpi_counts() / mpi_displs() member functions.

Defined in header <mpi/buffer.hpp>

template<typename T>
requires detail::traits_has_count<std::remove_cvref_t<T>>
auto count(T &&t)
(1)
template<typename T>
requires (!detail::traits_has_count<std::remove_cvref_t<T>>) && detail::has_count_member<T>
auto count(T &&t)
(2)
template<typename T>
requires (!detail::traits_has_count<std::remove_cvref_t<T>>) && (!detail::has_count_member<T>) && std::ranges::sized_range<T>
auto count(T &&t)
(3)
Returns:
  • (1) The number of elements of the buffer t, obtained via buffer_traits<T>::count(t).

  • (2) The number of elements of the buffer t, obtained via t.mpi_count().

  • (3) The number of elements of the sized range t, std::ranges::size(t).

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

template<typename T>
requires (detail::traits_has_const_ptr<std::remove_cvref_t<T>> || detail::traits_has_ptr<std::remove_cvref_t<T>>)
auto ptr(T &&t)
(1)
template<typename T>
requires (!detail::traits_has_const_ptr<std::remove_cvref_t<T>>) && (!detail::traits_has_ptr<std::remove_cvref_t<T>>) && detail::has_ptr_member<T>
auto ptr(T &&t)
(2)
template<typename T>
requires (!detail::traits_has_const_ptr<std::remove_cvref_t<T>>) && (!detail::traits_has_ptr<std::remove_cvref_t<T>>) && (!detail::has_ptr_member<T>) && std::ranges::contiguous_range<T>
auto ptr(T &&t)
(3)
Returns:
  • (1) The data pointer of the buffer t, obtained via buffer_traits<T>::ptr(t); points to const for a buffer only usable for sending.

  • (2) The data pointer of the buffer t, obtained via t.mpi_ptr().

  • (3) The data pointer of the contiguous range t, std::ranges::data(t).

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

template<typename T>
requires detail::traits_has_type<std::remove_cvref_t<T>>
auto type(T &&t)
(1)
template<typename T>
requires (!detail::traits_has_type<std::remove_cvref_t<T>>) && detail::has_type_member<T>
auto type(T &&t)
(2)
template<typename T>
requires (!detail::traits_has_type<std::remove_cvref_t<T>>) && (!detail::has_type_member<T>) && detail::range_of_builtin_mpi_type<T>
auto type(T&&)
(3)
Returns:
  • (1) The MPI datatype of the elements of the buffer t, obtained via buffer_traits<T>::type(t).

  • (2) The MPI datatype of the elements of the buffer t, obtained via t.mpi_type().

  • (3) The builtin MPI datatype of the element type of the range t, e.g. MPI_INT for int.

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

template<typename T>
requires detail::traits_has_counts<std::remove_cvref_t<T>>
decltype(auto) counts(T &&t)
(1)
template<typename T>
requires (!detail::traits_has_counts<std::remove_cvref_t<T>>) && detail::has_counts_member<T>
decltype(auto) counts(T &&t)
(2)
Returns:
  • (1) The per-rank counts of the variadic buffer t, obtained via buffer_traits<T>::counts(t).

  • (2) The per-rank counts of the variadic buffer t, obtained via t.mpi_counts().

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

template<typename T>
requires detail::traits_has_displs<std::remove_cvref_t<T>>
decltype(auto) displs(T &&t)
(1)
template<typename T>
requires (!detail::traits_has_displs<std::remove_cvref_t<T>>) && detail::has_displs_member<T>
decltype(auto) displs(T &&t)
(2)
Returns:
  • (1) The per-rank displacements of the variadic buffer t, obtained via buffer_traits<T>::displs(t).

  • (2) The per-rank displacements of the variadic buffer t, obtained via t.mpi_displs().

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

template<has_mpi_resize_for_receive T>
void resize_for_receive(T &t, std::ptrdiff_t n)
(1)
template<typename T>
requires (!has_mpi_resize_for_receive<T>) && has_resize<T>
void resize_for_receive(T &t, std::ptrdiff_t n)
(2)

(1) Resize t to hold n MPI elements before a receive. Dispatches to:

  1. t.mpi_resize_for_receive(n) — custom protocol (e.g. resize_and_overwrite, NUMA alloc)

  2. t.resize(n) — standard containers

Contract. The elements [0, min(old size, n)) keep their values; new elements are unspecified (they may be left uninitialized, which is what custom protocols are for). Like a receive, which only writes the range it has data for, a resize only touches what it has to. This allows growing a buffer that already holds received data, e.g. to receive further messages behind it. std::vector::resize, std::string::resize_and_overwrite and Kokkos::resize all behave this way.

(2) Resizes the standard container t to n elements before a receive; see the overload above for the contract.

Defined in header <kamping/v2/views/view_interface.hpp>

bool enable_borrowed_buffer

Opt-in trait for non-owning buffer types, mirroring std::ranges::enable_borrowed_range.

A “borrowed” buffer does not own its data: the underlying memory is managed externally and outlives the buffer object. Non-blocking operations use this to decide whether the buffer has to be handed back on completion (see iresult). The default covers any std::ranges::borrowed_range (e.g. std::span); kamping views propagate borrowedness from their base.

template <>
inline constexpr bool kamping::v2::enable_borrowed_buffer<MyView> = true;

Defined in header <kamping/v2/views/concepts.hpp>

template<typename T>
concept borrowed_buffer
template<typename T>
concept borrowed_buffer = enable_borrowed_buffer<std::remove_cvref_t<T>>

A buffer that does not own its data; see enable_borrowed_buffer.

Defined in header <kamping/v2/views/concepts.hpp>

MPI objects

template<typename T>
struct handle_traits

Trait class for non-intrusive MPI handle customization of types you don’t own.

Only implement the members you need; unimplemented members fall back to the mpi_handle() / mpi_handle_ptr() member functions.

template <>
struct mpi::experimental::handle_traits<MyComm> {
    static MPI_Comm  handle(MyComm const& c) { return c.get_comm(); }
    static MPI_Comm* handle_ptr(MyComm& c)   { return c.get_comm_ptr(); }
};

Defined in header <mpi/handle.hpp>

template<typename T>
requires detail::handle_traits_has_handle<std::remove_cvref_t<T>>
auto handle(T const &t)
(1)
template<typename T>
requires (!detail::handle_traits_has_handle<std::remove_cvref_t<T>>) && detail::has_mpi_handle_member<T>
auto handle(T const &t)
(2)
template<builtin_handle T>
T handle(T t) noexcept
(3)
Returns:
  • (1) The MPI handle of t, obtained via handle_traits<T>::handle(t).

  • (2) The MPI handle of t, obtained via t.mpi_handle().

  • (3) t itself, for a raw MPI handle.

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

template<typename T>
requires detail::handle_traits_has_handle_ptr<std::remove_cvref_t<T>>
auto handle_ptr(T &t)
(1)
template<typename T>
requires (!detail::handle_traits_has_handle_ptr<std::remove_cvref_t<T>>) && detail::has_mpi_handle_ptr_member<T>
auto handle_ptr(T &t)
(2)
template<builtin_handle T>
T *handle_ptr(T &t) noexcept
(3)
template<builtin_handle T>
T *handle_ptr(T *t) noexcept
(4)
Returns:
  • (1) A pointer to the MPI handle of t that MPI can write into, obtained via handle_traits<T>::handle_ptr(t).

  • (2) A pointer to the MPI handle of t that MPI can write into, obtained via t.mpi_handle_ptr().

  • (3) The address of the raw MPI handle t.

  • (4) t itself, for a pointer to a raw MPI handle (e.g. MPI_STATUS_IGNORE).

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

template<typename T, typename HandleType>
concept convertible_to_mpi_handle
template<typename T, typename HandleType>
concept convertible_to_mpi_handle = builtin_handle<HandleType> && requires(T const& t) {
    { handle(t) } -> std::same_as<HandleType>;
}

A type from which handle() extracts an MPI handle of type HandleType.

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

template<typename T, typename HandleType>
concept convertible_to_mpi_handle_ptr
template<typename T, typename HandleType>
concept convertible_to_mpi_handle_ptr = builtin_handle<HandleType> && requires(T& t) {
    { handle_ptr(t) } -> std::same_as<HandleType*>;
}

A type from which handle_ptr() extracts a writable pointer to an MPI handle of type HandleType.

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

Arrays of handles

template<typename T>
struct handle_range_traits

Trait class for non-intrusively exposing the contiguous handle storage of a type you don’t own. Specialize with static <contiguous_handle_range> handle_range(T& t); return a reference or a borrowed view (e.g. std::span) into t, never a copy.

Defined in header <mpi/handle.hpp>

template<typename T>
requires detail::handle_range_traits_has_handle_range<std::remove_cvref_t<T>>
decltype(auto) handle_range(T &&t)
(1)
template<typename T>
requires (!detail::handle_range_traits_has_handle_range<std::remove_cvref_t<T>>) && detail::has_mpi_handle_range_member<T>
decltype(auto) handle_range(T &&t)
(2)
template<typename T>
requires (!detail::handle_range_traits_has_handle_range<std::remove_cvref_t<T>>) && (!detail::has_mpi_handle_range_member<T>) && contiguous_handle_range<T>
T &handle_range(T &t) noexcept
(3)
Returns:
  • (1) The contiguous handle storage of t, obtained via handle_range_traits<T>::handle_range(t).

  • (2) The contiguous handle storage of t, obtained via t.mpi_handle_range().

  • (3) t itself, for an lvalue contiguous range of raw MPI handles.

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

template<typename T, typename HandleType>
concept convertible_to_mpi_handle_range
template<typename T, typename HandleType>
concept convertible_to_mpi_handle_range = builtin_handle<HandleType> && requires(T& t) {
    { handle_range(t) } -> contiguous_handle_range_of<HandleType>;
}

A type whose handle_range() is a writable, contiguous range of HandleType that refers to the caller’s storage: either an lvalue reference or a borrowed view (never a container returned by value, since MPI writes into it).

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

Ranks and tags

template<typename T>
struct rank_traits

Trait class for non-intrusively converting a type you don’t own to an MPI rank.

Scoped enums and types implicitly convertible to int need no specialization.

template <>
struct mpi::experimental::rank_traits<Rank> {
    static int rank(Rank const& r) { return r.value; }
};

Defined in header <mpi/handle.hpp>

template<typename T>
struct tag_traits

Trait class for non-intrusively converting a type you don’t own to an MPI tag; see rank_traits.

Defined in header <mpi/handle.hpp>

template<typename T>
requires detail::rank_traits_has_rank<std::remove_cvref_t<T>>
int to_rank(T const &t)
(1)
template<typename T>
requires (!detail::rank_traits_has_rank<std::remove_cvref_t<T>>) && detail::is_scoped_enum_v<std::remove_cvref_t<T>>
int to_rank(T t)
(2)
template<typename T>
requires (!detail::rank_traits_has_rank<std::remove_cvref_t<T>>) && (!detail::is_scoped_enum_v<std::remove_cvref_t<T>>) && std::convertible_to<T, int>
int to_rank(T t)
(3)
Returns:
  • (1) The MPI rank t stands for, obtained via rank_traits<T>::rank(t).

  • (2) The underlying value of the scoped enum t, as an MPI rank.

  • (3) t converted to int, as an MPI rank.

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

template<typename T>
requires detail::tag_traits_has_tag<std::remove_cvref_t<T>>
int to_tag(T const &t)
(1)
template<typename T>
requires (!detail::tag_traits_has_tag<std::remove_cvref_t<T>>) && detail::is_scoped_enum_v<std::remove_cvref_t<T>>
int to_tag(T t)
(2)
template<typename T>
requires (!detail::tag_traits_has_tag<std::remove_cvref_t<T>>) && (!detail::is_scoped_enum_v<std::remove_cvref_t<T>>) && std::convertible_to<T, int>
int to_tag(T t)
(3)
Returns:
  • (1) The MPI tag t stands for, obtained via tag_traits<T>::tag(t).

  • (2) The underlying value of the scoped enum t, as an MPI tag.

  • (3) t converted to int, as an MPI tag.

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

template<typename T>
concept rank
template<typename T>
concept rank = requires(T const& t) {
    { to_rank(t) } -> std::same_as<int>;
}

A type usable as an MPI rank: int, a scoped enum, or a type adapted via rank_traits.

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

template<typename T>
concept tag
template<typename T>
concept tag = requires(T const& t) {
    { to_tag(t) } -> std::same_as<int>;
}

A type usable as an MPI tag: int, a scoped enum, or a type adapted via tag_traits.

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

Writing your own views

A view wraps a buffer, forwards the accessors it does not override and overrides the others as members. Derive from view_interface to get the forwarding, and use adaptor or adaptor_closure to make it pipeable.

template<typename Derived>
struct view_interface

CRTP base of all kamping views.

Derived implements base(), returning the wrapped buffer. view_interface forwards the range interface and every member of the buffer protocol (mpi_ptr(), mpi_count(), mpi_type(), mpi_counts(), …) and of the deferred buffer protocols (set_recv_count(), set_comm_size(), commit_counts(), materialize()) to it, so a view only implements the members it changes. A forwarder only exists if the wrapped buffer provides the member.

Defined in header <kamping/v2/views/view_interface.hpp>

template<typename Self = Derived>
requires has_materialize<decltype(std::declval<Self&>().base())>
void materialize()

Forwards eager realization to the wrapped base, so a deferred resizing view (resize_view / resize_v_view) can still be materialized when nested inside other view layers. Use the free function kamping::v2::materialize() rather than calling this directly.

template<typename Self = Derived>
requires deferred_recv_buf<std::remove_cvref_t<decltype(std::declval<Self&>().base())>>
void set_recv_count(std::ptrdiff_t n)

Forwards the single-count deferred protocol (set_recv_count) so a resize_view nested inside other view layers stays a deferred_recv_buf.

auto &underlying() &
(1)
auto const &underlying() const &
(2)
auto underlying() &&
(3)

(1) Returns the container (or other buffer) at the bottom of a chain of views, e.g. the std::vector in std::vector<int>{} | views::resize.

Use it to get your container back from the result of an operation. On an rvalue, the container is moved out; on an lvalue, a reference to it is returned:

std::vector<int> data = kamping::v2::recv(std::vector<int>{} | views::resize, source, comm).underlying();
auto [_, recv] = kamping::v2::allgatherv(local, kamping::v2::auto_recv_v<int>(), comm);
std::vector<int>& all = recv.underlying();

(2) Like the overload above, for a const view.

(3) Like the overload above, for an rvalue view: moves the container out, without copying it.

template<typename T>
concept view
template<typename T>
concept view = std::ranges::view<T> || std::derived_from<T, view_interface_base>

A type that is a “view” in the kamping sense: either a std::ranges::view (lightweight, copyable, e.g. std::span) or a kamping view (derived from view_interface_base — may be owning and non-copyable like owning_view). Mirrors the role of the std::ranges::view concept but extended for kamping.

Defined in header <kamping/v2/views/view_interface.hpp>

template<typename R>
auto all(R &&r)

Wraps a value in the appropriate view, mirroring std::views::all extended for kamping:

  • std::ranges::view (span, string_view, …) → copy/move through; no extra wrapping to avoid libc++ constraint circularity bugs with these types.

  • kamping view (derived from view_interface_base, may be owning/non-copyable): lvalue → ref_view (borrow), rvalue → move through (no wrapping needed).

  • any other lvalue → ref_view

  • any other rvalue → owning_view

Defined in header <kamping/v2/views/all.hpp>

template<typename T>
class ref_view

Non-owning view over an lvalue. T is unconstrained — supports both ranges and non-range buffer types (e.g. a struct with mpi_ptr()/mpi_count() but no begin()/end()). All MPI protocol methods (mpi_ptr, mpi_count, mpi_type, mpi_resize_for_receive, …) are forwarded automatically via view_interface.

Defined in header <kamping/v2/views/all.hpp>

template<std::movable T>
class owning_view

Owning view over an rvalue. Moves the object in and owns it. Same protocol forwarding as ref_view via view_interface.

Defined in header <kamping/v2/views/all.hpp>

template<std::size_t ExtraArgs, typename Fn>
struct adaptor

Generic range adaptor factory, parameterized by the number of extra arguments (beyond the value). Arity-based disambiguation (like libstdc++ internally):

  • ExtraArgs arguments → partial application, returns a pipeable partial_adaptor

  • ExtraArgs + 1 arguments → full call, first argument is the value

Usage: inline constexpr adaptor<1, decltype([](auto&& r, MPI_Datatype t) { … })> with_type{}; vec | with_type(MPI_INT) // partial → partial_adaptor → pipe applies it with_type(vec, MPI_INT) // full call

Defined in header <kamping/v2/views/adaptor.hpp>

template<typename Derived>
struct adaptor_closure

CRTP base that makes Derived pipe-able. Provides two operator| overloads found via ADL:

  • val | closure → closure(val) when val is NOT itself a closure

  • closure1 | closure2 → composed_closure{…} when both sides are closures The !is_adaptor_closure constraint on the left side is intentionally the only constraint, so any value type (range, MPI buffer, scalar, …) can appear on the left side of |.

Defined in header <kamping/v2/views/adaptor.hpp>

template<typename Derived>
struct adaptor_factory

CRTP base of an adaptor that takes optional arguments, such as views::auto_counts: calling it returns a closure (auto_counts(), auto_counts(counts)), and the factory itself stands for its argument-less closure in a pipe, so buf | auto_counts is buf | auto_counts(). Derived must be callable without arguments.

A factory cannot simply be a closure applying itself to a buffer, since a one-argument call already means something else (e.g. auto_counts(counts) passes the counts storage).

Defined in header <kamping/v2/views/adaptor.hpp>