KaMPIng 0.1.1
Flexible and (near) zero-overhead C++ bindings for MPI
Loading...
Searching...
No Matches
mpi_datatype.hpp
Go to the documentation of this file.
1// This file is part of KaMPIng.
2//
3// Copyright 2021-2024 The KaMPIng Authors
4//
5// KaMPIng is free software : you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
7// version. KaMPIng is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
8// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
9// for more details.
10//
11// You should have received a copy of the GNU Lesser General Public License along with KaMPIng. If not, see
12// <https://www.gnu.org/licenses/>.
13
14/// @file
15/// @brief Utility that maps C++ types to types that can be understood by MPI.
16
17#pragma once
18
19#include <type_traits>
20
21#include <kassert/kassert.hpp>
22#include <mpi.h>
23#ifdef KAMPING_ENABLE_REFLECTION
24 #include <boost/pfr.hpp>
25#endif
26
29#include "kamping/noexcept.hpp"
30
31namespace kamping {
32/// @brief Tag used for indicating that a struct is reflectable.
33/// @see struct_type
34struct kamping_tag {};
35} // namespace kamping
36
37namespace kamping {
38/// @addtogroup kamping_mpi_utility
39/// @{
40///
41///
42
43namespace internal {
44/// @brief Helper to check if a type is a `std::pair`.
45template <typename T>
46struct is_std_pair : std::false_type {};
47/// @brief Helper to check if a type is a `std::pair`.
48template <typename T1, typename T2>
49struct is_std_pair<std::pair<T1, T2>> : std::true_type {};
50
51/// @brief Helper to check if a type is a `std::tuple`.
52template <typename T>
53struct is_std_tuple : std::false_type {};
54/// @brief Helper to check if a type is a `std::tuple`.
55template <typename... Ts>
56struct is_std_tuple<std::tuple<Ts...>> : std::true_type {};
57
58/// @brief Helper to check if a type is a `std::array`.
59template <typename A>
60struct is_std_array : std::false_type {};
61
62/// @brief Helper to check if a type is a `std::array`.
63template <typename T, size_t N>
64struct is_std_array<std::array<T, N>> : std::true_type {
65 using value_type = T; ///< The type of the elements in the array.
66 static constexpr size_t size = N; ///< The number of elements in the array.
67};
68} // namespace internal
69
70/// @brief Constructs an contiguous type of size \p N from type \p T using `MPI_Type_contiguous`.
71template <typename T, size_t N>
73 static constexpr TypeCategory category = TypeCategory::contiguous; ///< The category of the type.
74 /// @brief Whether the type has to be committed before it can be used in MPI calls.
76 /// @brief The MPI_Datatype corresponding to the type.
78};
79
80/// @brief Constructs a type that is serialized as a sequence of `sizeof(T)` bytes using `MPI_BYTE`. Note that you must
81/// ensure that this conversion is valid.
82template <typename T>
83struct byte_serialized : contiguous_type<std::byte, sizeof(T)> {};
84
85/// @brief Constructs a MPI_Datatype for a struct-like type.
86/// @tparam T The type to construct the MPI_Datatype for.
87///
88/// This requires that \p T is a `std::pair`, `std::tuple` or a type that is reflectable with
89/// [pfr](https://github.com/boostorg/pfr). If you do not agree with PFR's decision if a type is implicitly
90/// reflectable, you can override it by providing a specialization of \c pfr::is_reflectable with the tag \ref
91/// kamping_tag.
92/// @see https://apolukhin.github.io/pfr_non_boost/pfr/is_reflectable.html
93/// https://www.boost.org/doc/libs/master/doc/html/reference_section_of_pfr.htmlfor details
94/// @note Reflection support for arbitrary struct types is only suppported when KaMPIng is compiled with PFR.
95template <typename T>
97#ifdef KAMPING_ENABLE_REFLECTION
98 static_assert(
100 || boost::pfr::is_implicitly_reflectable<T, kamping_tag>::value,
101 "Type must be a std::pair, std::tuple or reflectable"
102 );
103#else
104 static_assert(
105 internal::is_std_pair<T>::value || internal::is_std_tuple<T>::value, "Type must be a std::pair or std::tuple"
106 );
107#endif
108 /// @brief The category of the type.
109 static constexpr TypeCategory category = TypeCategory::struct_like;
110 /// @brief Whether the type has to be committed before it can be used in MPI calls.
112 /// @brief The MPI_Datatype corresponding to the type.
114};
115
116/// @brief Type tag for indicating that no static type definition exists for a type.
118
119/// @brief The type dispatcher that maps a C++ type \p T to a type trait that can be used to construct an MPI_Datatype.
120///
121/// The mapping is as follows:
122/// - C++ types directly supported by MPI are mapped to the corresponding `MPI_Datatype`.
123/// - Enums are mapped to the underlying type.
124/// - C-style arrays and `std::array` are mapped to contiguous types of the underlying type.
125/// - All other trivially copyable types are mapped to a contiguous type consisting of `sizeof(T)` bytes.
126/// - All other types are not supported directly and require a specialization of `mpi_type_traits`. In this case, the
127/// trait `no_matching_type` is returned.
128///
129/// @returns The corresponding type trait for the type \p T.
130template <typename T>
132 using T_no_const = std::remove_const_t<T>; // remove const from T
133 // we previously also removed volatile here, but interpreting a pointer
134 // to a volatile type as a pointer to a non-volatile type is UB
135
136 static_assert(
137 !std::is_pointer_v<T_no_const>,
138 "MPI does not support pointer types. Why do you want to transfer a pointer over MPI?"
139 );
140
141 static_assert(!std::is_function_v<T_no_const>, "MPI does not support function types.");
142
143 // TODO: this might be a bit too strict. We might want to allow unions in the future.
144 static_assert(!std::is_union_v<T_no_const>, "MPI does not support union types.");
145
146 static_assert(!std::is_void_v<T_no_const>, "There is no MPI datatype corresponding to void.");
147
148 if constexpr (is_builtin_type_v<T_no_const>) {
149 // builtin types are handled by the builtin_type trait
151 } else if constexpr (std::is_enum_v<T_no_const>) {
152 // enums are mapped to the underlying type
154 } else if constexpr (std::is_array_v<T_no_const>) {
155 // arrays are mapped to contiguous types
156 constexpr size_t array_size = std::extent_v<T_no_const>;
157 using underlying_type = std::remove_extent_t<T_no_const>;
159 } else if constexpr (internal::is_std_array<T_no_const>::value) {
160 // std::array is mapped to contiguous types
164 } else if constexpr (std::is_trivially_copyable_v<T_no_const>) {
165 // all other trivially copyable types are mapped to a sequence of bytes
167 } else {
168 return no_matching_type{};
169 }
170}
171
172/// @brief The type trait that maps a C++ type \p T to a type trait that can be used to construct an MPI_Datatype.
173///
174/// The default behavior is controlled by \ref type_dispatcher. If you want to support a type that is not supported by
175/// the default behavior, you can specialize this trait. For example:
176///
177/// ```cpp
178/// struct MyType {
179/// int a;
180/// double b;
181/// char c;
182/// std::array<int, 3> d;
183/// };
184/// namespace kamping {
185/// // using KaMPIng's built-in struct serializer
186/// template <>
187/// struct mpi_type_traits<MyType> : struct_type<MyType> {};
188///
189/// // or using an explicitly constructed type
190/// template <>
191/// struct mpi_type_traits<MyType> {
192/// static constexpr bool has_to_be_committed = true;
193/// static MPI_Datatype data_type() {
194/// MPI_Datatype type;
195/// MPI_Type_create_*(..., &type);
196/// return type;
197/// }
198/// };
199/// } // namespace kamping
200/// ```
201///
202template <typename T, typename Enable = void>
204
205/// @brief The type trait that maps a C++ type \p T to a type trait that can be used to construct an MPI_Datatype.
206///
207/// The default behavior is controlled by \ref type_dispatcher. If you want to support a type that is not supported by
208/// the default behavior, you can specialize this trait. For example:
209///
210/// ```cpp
211/// struct MyType {
212/// int a;
213/// double b;
214/// char c;
215/// std::array<int, 3> d;
216/// };
217/// namespace kamping {
218/// // using KaMPIng's built-in struct serializer
219/// template <>
220/// struct mpi_type_traits<MyType> : struct_type<MyType> {};
221///
222/// // or using an explicitly constructed type
223/// template <>
224/// struct mpi_type_traits<MyType> {
225/// static constexpr bool has_to_be_committed = true;
226/// static MPI_Datatype data_type() {
227/// MPI_Datatype type;
228/// MPI_Type_create_*(..., &type);
229/// return type;
230/// }
231/// };
232/// } // namespace kamping
233/// ```
234///
235template <typename T>
236struct mpi_type_traits<T, std::enable_if_t<!std::is_same_v<decltype(type_dispatcher<T>()), no_matching_type>>> {
237 /// @brief The base type of this trait obtained via \ref type_dispatcher.
238 /// This defines how the data type is constructed in \c mpi_type_traits::data_type().
239 using base = decltype(type_dispatcher<T>());
240 /// @brief The category of the type.
241 static constexpr TypeCategory category = base::category;
242 /// @brief Whether the type has to be committed before it can be used in MPI calls.
243 static constexpr bool has_to_be_committed = category_has_to_be_committed(category);
244 /// @brief The MPI_Datatype corresponding to the type T.
246 return decltype(type_dispatcher<T>())::data_type();
247 }
248};
249
250///@brief Check if the type has a static type definition, i.e. \ref mpi_type_traits is defined.
251template <typename, typename Enable = void>
252struct has_static_type : std::false_type {};
253
254///@brief Check if the type has a static type definition, i.e. \ref mpi_type_traits is defined.
255template <typename T>
256struct has_static_type<T, std::void_t<decltype(mpi_type_traits<T>::data_type())>> : std::true_type {};
257
258///@brief Check if the type has a static type definition, i.e. has a corresponding \c MPI_Datatype defined following the
259/// rules from \ref type_dispatcher.
260template <typename T>
262
263/// @brief Register a new \c MPI_Datatype for \p T with the MPI environment. It will be freed when the environment is
264/// finalized.
265///
266/// The \c MPI_Datatype is created using \c mpi_type_traits<T>::data_type() and committed using \c MPI_Type_commit.
267///
268/// @tparam T The type to register.
269template <typename T>
272 MPI_Type_commit(&type);
273 KASSERT(type != MPI_DATATYPE_NULL);
274 mpi_env.register_mpi_type(type);
275 return type;
276}
277
278/// @brief Translate template parameter T to an MPI_Datatype. If no corresponding MPI_Datatype exists, we will create
279/// new continuous type.
280/// Based on https://gist.github.com/2b-t/50d85115db8b12ed263f8231abf07fa2
281/// To check if type \c T maps to a builtin \c MPI_Datatype at compile-time, use \c mpi_type_traits.
282/// @tparam T The type to translate into an MPI_Datatype.
283/// @return The tag identifying the corresponding MPI_Datatype or the newly created type.
284/// @see mpi_custom_continuous_type()
285///
286
287/// @brief Translate type \p T to an MPI_Datatype using the type defined via \ref mpi_type_traits.
288///
289/// If the type has not been registered with MPI yet, it will be created and committed and automatically registered with
290/// the MPI environment, such that it will be freed when the environment is finalized.
291///
292/// @tparam T The type to translate into an MPI_Datatype.
293template <typename T>
295 static_assert(
297 "\n --> Type not supported directly by KaMPIng. Please provide a specialization for mpi_type_traits."
298 );
300 // using static initialization to ensure that the type is only committed once
302 return type;
303 } else {
305 }
306}
307
308template <typename T, size_t N>
310 MPI_Datatype type;
312 if constexpr (std::is_same_v<T, std::byte>) {
314 } else {
315 static_assert(
317 "\n --> Type not supported directly by KaMPIng. Please provide a specialization for mpi_type_traits."
318 );
320 }
321 int count = static_cast<int>(N);
322 int err = MPI_Type_contiguous(count, base_type, &type);
324 return type;
325}
326
327namespace internal {
328/// @brief Applies functor \p f to each field of the tuple with an index in index sequence \p Is.
329///
330/// \p f should be a callable that takes a reference to the field and its index.
331template <typename T, typename F, size_t... Is>
332void for_each_tuple_field(T&& t, F&& f, std::index_sequence<Is...>) {
333 (f(std::get<Is>(std::forward<T>(t)), Is), ...);
334}
335
336/// @brief Applies functor \p f to each field of the tuple \p t.
337///
338/// \p f should be a callable that takes a reference to the field and its index.
339template <typename T, typename F>
340void for_each_tuple_field(T& t, F&& f) {
341 for_each_tuple_field(t, std::forward<F>(f), std::make_index_sequence<std::tuple_size_v<T>>{});
342}
343
344/// @brief Applies functor \p f to each field of the tuple-like type \p t.
345/// This works for `std::pair` and `std::tuple`. If KaMPIng's reflection support is enabled, this also works with types
346/// that are reflectable with [pfr](https://github.com/boostorg/pfr).
347///
348/// \p f should be a callable that takes a reference to the field and
349/// its index.
350template <typename T, typename F>
351void for_each_field(T& t, F&& f) {
353 for_each_tuple_field(t, std::forward<F>(f));
354 } else {
355#ifdef KAMPING_ENABLE_REFLECTION
356 boost::pfr::for_each_field(t, std::forward<F>(f));
357#else
358 // should not happen
360#endif
361 }
362}
363
364/// @brief The number of elements in a tuple-like type.
365/// This works for `std::pair` and `std::tuple`.
366/// If KaMPIng's reflection support is enabled, this also works with types that are reflectable with
367/// [pfr](https://github.com/boostorg/pfr).
368template <typename T>
369constexpr size_t tuple_size = [] {
370 if constexpr (internal::is_std_pair<T>::value) {
371 return 2;
372 } else if constexpr (internal::is_std_tuple<T>::value) {
373 return std::tuple_size_v<T>;
374 } else {
375#ifdef KAMPING_ENABLE_REFLECTION
376 return boost::pfr::tuple_size_v<T>;
377#else
378 if constexpr (std::is_arithmetic_v<T>) {
379 return 1;
380 } else {
381 return std::tuple_size_v<T>;
382 }
383#endif
384 }
385}();
386} // namespace internal
387
388template <typename T>
390 T t{};
391 MPI_Aint base;
392 MPI_Get_address(&t, &base);
393 int blocklens[internal::tuple_size<T>];
394 MPI_Datatype types[internal::tuple_size<T>];
395 MPI_Aint disp[internal::tuple_size<T>];
396 internal::for_each_field(t, [&](auto& elem, size_t i) {
398 using elem_type = std::remove_reference_t<decltype(elem)>;
399 static_assert(
401 "\n --> Type not supported directly by KaMPIng. Please provide a specialization for mpi_type_traits."
402 );
404 disp[i] = MPI_Aint_diff(disp[i], base);
405 blocklens[i] = 1;
406 });
407 MPI_Datatype type;
408 int err = MPI_Type_create_struct(static_cast<int>(internal::tuple_size<T>), blocklens, disp, types, &type);
411 err = MPI_Type_create_resized(type, 0, sizeof(T), &resized_type);
413 return resized_type;
414}
415
416/// @brief A scoped MPI_Datatype that commits the type on construction and frees it on destruction.
417/// This is useful for RAII-style management of MPI_Datatype objects.
419 MPI_Datatype _type; ///< The MPI_Datatype.
420public:
421 /// @brief Construct a new scoped MPI_Datatype and commits it. If no type is provided, default to
422 /// `MPI_DATATYPE_NULL` and does not commit or free anything.
424 if (type != MPI_DATATYPE_NULL) {
425 mpi_env.commit(type);
426 }
427 }
428 /// @brief Deleted copy constructor.
430 /// @brief Deleted copy assignment.
432
433 /// @brief Move constructor.
437 /// @brief Move assignment.
439 std::swap(_type, other._type);
440 return *this;
441 }
442 /// @brief Get the MPI_Datatype.
443 MPI_Datatype const& data_type() const {
444 return _type;
445 }
446 /// @brief Free the MPI_Datatype.
448 if (_type != MPI_DATATYPE_NULL) {
449 mpi_env.free(_type);
450 }
451 }
452};
453
454/// @}
455
456} // namespace kamping
Mapping of C++ datatypes to builtin MPI types.
STL-compatible allocator for requesting memory using the builtin MPI allocator.
Definition allocator.hpp:32
A scoped MPI_Datatype that commits the type on construction and frees it on destruction....
Definition mpi_datatype.hpp:418
ScopedDatatype(ScopedDatatype const &)=delete
Deleted copy constructor.
ScopedDatatype & operator=(ScopedDatatype &&other) noexcept
Move assignment.
Definition mpi_datatype.hpp:438
ScopedDatatype(ScopedDatatype &&other) noexcept
Move constructor.
Definition mpi_datatype.hpp:434
ScopedDatatype & operator=(ScopedDatatype const &)=delete
Deleted copy assignment.
ScopedDatatype(MPI_Datatype type=MPI_DATATYPE_NULL)
Construct a new scoped MPI_Datatype and commits it. If no type is provided, default to MPI_DATATYPE_N...
Definition mpi_datatype.hpp:423
~ScopedDatatype()
Free the MPI_Datatype.
Definition mpi_datatype.hpp:447
MPI_Datatype const & data_type() const
Get the MPI_Datatype.
Definition mpi_datatype.hpp:443
Wrapper for MPI functions that don't require a communicator.
Environment< InitMPIMode::NoInitFinalize > const mpi_env
A global environment object to use when you don't want to create a new Environment object.
Definition environment.hpp:323
#define THROW_IF_MPI_ERROR(error_code, function)
Wrapper around THROWING_KASSERT for MPI errors.
Definition error_handling.hpp:33
auto type_dispatcher()
The type dispatcher that maps a C++ type T to a type trait that can be used to construct an MPI_Datat...
Definition mpi_datatype.hpp:131
static constexpr bool has_static_type_v
Check if the type has a static type definition, i.e. has a corresponding MPI_Datatype defined followi...
Definition mpi_datatype.hpp:261
static MPI_Datatype data_type()
The MPI_Datatype corresponding to the type.
Definition mpi_datatype.hpp:389
MPI_Datatype mpi_datatype() KAMPING_NOEXCEPT
Translate template parameter T to an MPI_Datatype. If no corresponding MPI_Datatype exists,...
Definition mpi_datatype.hpp:294
static MPI_Datatype data_type()
The MPI_Datatype corresponding to the type.
Definition mpi_datatype.hpp:309
constexpr bool category_has_to_be_committed(TypeCategory category)
Checks if a type of the given category has to commited before usage in MPI calls.
Definition builtin_types.hpp:35
TypeCategory
the members specify which group the datatype belongs to according to the type groups specified in Sec...
Definition builtin_types.hpp:32
MPI_Datatype construct_and_commit_type()
Register a new MPI_Datatype for T with the MPI environment. It will be freed when the environment is ...
Definition mpi_datatype.hpp:270
void for_each_tuple_field(T &&t, F &&f, std::index_sequence< Is... >)
Applies functor f to each field of the tuple with an index in index sequence Is.
Definition mpi_datatype.hpp:332
void for_each_field(T &t, F &&f)
Applies functor f to each field of the tuple-like type t. This works for std::pair and std::tuple....
Definition mpi_datatype.hpp:351
constexpr size_t tuple_size
The number of elements in a tuple-like type. This works for std::pair and std::tuple....
Definition mpi_datatype.hpp:369
STL namespace.
Defines the macro KAMPING_NOEXCEPT to be used instad of noexcept.
#define KAMPING_NOEXCEPT
noexcept macro.
Definition noexcept.hpp:19
Constructs a type that is serialized as a sequence of sizeof(T) bytes using MPI_BYTE....
Definition mpi_datatype.hpp:83
Constructs an contiguous type of size N from type T using MPI_Type_contiguous.
Definition mpi_datatype.hpp:72
static constexpr bool has_to_be_committed
Whether the type has to be committed before it can be used in MPI calls.
Definition mpi_datatype.hpp:75
static constexpr TypeCategory category
Definition mpi_datatype.hpp:73
Check if the type has a static type definition, i.e. mpi_type_traits is defined.
Definition mpi_datatype.hpp:252
T value_type
The type of the elements in the array.
Definition mpi_datatype.hpp:65
Helper to check if a type is a std::array.
Definition mpi_datatype.hpp:60
Helper to check if a type is a std::pair.
Definition mpi_datatype.hpp:46
Helper to check if a type is a std::tuple.
Definition mpi_datatype.hpp:53
Tag used for indicating that a struct is reflectable.
Definition mpi_datatype.hpp:34
static MPI_Datatype data_type()
The MPI_Datatype corresponding to the type T.
Definition mpi_datatype.hpp:245
The type trait that maps a C++ type T to a type trait that can be used to construct an MPI_Datatype.
Definition mpi_datatype.hpp:203
Type tag for indicating that no static type definition exists for a type.
Definition mpi_datatype.hpp:117
Constructs a MPI_Datatype for a struct-like type.
Definition mpi_datatype.hpp:96
static constexpr TypeCategory category
The category of the type.
Definition mpi_datatype.hpp:109
static constexpr bool has_to_be_committed
Whether the type has to be committed before it can be used in MPI calls.
Definition mpi_datatype.hpp:111