diff --git a/CMakeLists.txt b/CMakeLists.txt index 43c6520c8..1169ffdbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,12 @@ option(KAMPING_BUILD_EXAMPLES_AND_TESTS OFF) option(KAMPING_TESTS_DISCOVER OFF) option(KAMPING_ENABLE_ULFM "Enable User-Level Failure-Mitigation (ULFM)" OFF) option(KAMPING_ENABLE_SERIALIZATION "Enable support for serialization (requires Cereal)" ON) +option(KAMPING_ENABLE_REFLECTION "Enable support for reflecting struct members (requires Boost.PFR)" ON) +option( + KAMPING_REFLECTION_USE_SYSTEM_BOOST_FOR_PFR + "Use Boost.PFR from system installed Boost, instead of using a standalone PFR install or building PFR from source." + OFF +) # Enable compilation with ccache. Defaults to ON if this is the main project. if (PROJECT_IS_TOP_LEVEL) @@ -190,22 +196,51 @@ FetchContent_MakeAvailable(kassert) target_link_libraries(kamping_base INTERFACE kassert::kassert) -# TODO: make this dependency optional FetchContent_Declare( pfr - GIT_REPOSITORY https://github.com/apolukhin/pfr_non_boost + GIT_REPOSITORY https://github.com/boostorg/pfr GIT_TAG 2.2.0 - SYSTEM + SYSTEM FIND_PACKAGE_ARGS 2.2.0 ) -FetchContent_MakeAvailable(pfr) -target_link_libraries(kamping_base INTERFACE Boost::pfr) + +if (KAMPING_ENABLE_REFLECTION) + if (KAMPING_REFLECTION_USE_SYSTEM_BOOST_FOR_PFR) + find_package(Boost 1.75 COMPONENTS headers CONFIG) + if (NOT Boost_FOUND) + message( + FATAL_ERROR + "Boost.PFR: No compatible Boost version found. Use KAMPING_REFLECTION_USE_SYSTEM_BOOST_FOR_PFR=OFF to use standalone Boost.PFR." + ) + else () + message(STATUS "Found Boost ${Boost_VERSION}: ${Boost_DIR}") + message(STATUS "Using system Boost for Boost.PFR") + add_library(kamping_pfr INTERFACE) + # when using system installed Boost, it does not provide a PFR target, so we have to link to the headers + # target + target_link_libraries(kamping_pfr INTERFACE Boost::headers) + add_library(Boost::pfr ALIAS kamping_pfr) + endif () + else () + FetchContent_MakeAvailable(pfr) + if (pr_FOUND) + message(STATUS "Found Boost.PFR: ${pfr_DIR}") + else () + message(STATUS "Boost.PFR: building from source.") + endif () + endif () + target_link_libraries(kamping_base INTERFACE Boost::pfr) + target_compile_definitions(kamping_base INTERFACE KAMPING_ENABLE_REFLECTION) + message(STATUS "Reflection: enabled") +else () + message(STATUS "Reflection: disabled") +endif () if (KAMPING_ENABLE_SERIALIZATION) FetchContent_Declare( cereal GIT_REPOSITORY https://github.com/USCiLab/cereal GIT_TAG v1.3.2 - SYSTEM FIND_PACKAGE_ARGS NAMES cereal 1.3.2 + SYSTEM FIND_PACKAGE_ARGS 1.3.2 ) set(JUST_INSTALL_CEREAL ON) FetchContent_MakeAvailable(cereal) diff --git a/include/kamping/distributed_graph_communicator.hpp b/include/kamping/distributed_graph_communicator.hpp index c2b07e26a..044dded40 100644 --- a/include/kamping/distributed_graph_communicator.hpp +++ b/include/kamping/distributed_graph_communicator.hpp @@ -147,11 +147,18 @@ namespace internal { template constexpr bool are_neighborhoods_weighted() { using NeighborType = typename NeighborhoodRange::value_type; +#ifdef KAMPING_ENABLE_REFLECTION static_assert( kamping::internal::tuple_size == 1 || kamping::internal::tuple_size == 2, "Neighbor type has to be a scalar (in the unweighted case) or pair-like (in the weighted case) type" ); return kamping::internal::tuple_size == 2; +#else + // If reflection is not available, we assume that the neighbor type is a pair-like type if it is not integral. + // This is the best we can do, because kamping::internal::tuple_size does not work with arbitrary types without + // reflection. + return !std::is_integral_v; +#endif } } // namespace internal diff --git a/include/kamping/mpi_datatype.hpp b/include/kamping/mpi_datatype.hpp index e8678570d..87e8bbecf 100644 --- a/include/kamping/mpi_datatype.hpp +++ b/include/kamping/mpi_datatype.hpp @@ -20,7 +20,9 @@ #include #include -#include +#ifdef KAMPING_ENABLE_REFLECTION + #include +#endif #include "kamping/builtin_types.hpp" #include "kamping/environment.hpp" @@ -84,17 +86,25 @@ struct byte_serialized : contiguous_type {}; /// @tparam T The type to construct the MPI_Datatype for. /// /// This requires that \p T is a `std::pair`, `std::tuple` or a type that is reflectable with -/// [pfr](https://github.com/apolukhin/pfr_non_boost). If you do not agree with PFR's decision if a type is implicitly +/// [pfr](https://github.com/boostorg/pfr). If you do not agree with PFR's decision if a type is implicitly /// reflectable, you can override it by providing a specialization of \c pfr::is_reflectable with the tag \ref /// kamping_tag. -/// @see https://apolukhin.github.io/pfr_non_boost/pfr/is_reflectable.html for details +/// @see https://apolukhin.github.io/pfr_non_boost/pfr/is_reflectable.html +/// https://www.boost.org/doc/libs/master/doc/html/reference_section_of_pfr.htmlfor details +/// @note Reflection support for arbitrary struct types is only suppported when KaMPIng is compiled with PFR. template struct struct_type { +#ifdef KAMPING_ENABLE_REFLECTION static_assert( internal::is_std_pair::value || internal::is_std_tuple::value - || pfr::is_implicitly_reflectable::value, + || boost::pfr::is_implicitly_reflectable::value, "Type must be a std::pair, std::tuple or reflectable" ); +#else + static_assert( + internal::is_std_pair::value || internal::is_std_tuple::value, "Type must be a std::pair or std::tuple" + ); +#endif /// @brief The category of the type. static constexpr TypeCategory category = TypeCategory::struct_like; /// @brief Whether the type has to be committed before it can be used in MPI calls. @@ -332,8 +342,8 @@ void for_each_tuple_field(T& t, F&& f) { } /// @brief Applies functor \p f to each field of the tuple-like type \p t. -/// This works for `std::pair` and `std::tuple` as well as types that are reflectable with -/// [pfr](https://github.com/apolukhin/pfr_non_boost). +/// This works for `std::pair` and `std::tuple`. If KaMPIng's reflection support is enabled, this also works with types +/// that are reflectable with [pfr](https://github.com/boostorg/pfr). /// /// \p f should be a callable that takes a reference to the field and /// its index. @@ -342,13 +352,19 @@ void for_each_field(T& t, F&& f) { if constexpr (internal::is_std_pair::value || internal::is_std_tuple::value) { for_each_tuple_field(t, std::forward(f)); } else { - pfr::for_each_field(t, std::forward(f)); +#ifdef KAMPING_ENABLE_REFLECTION + boost::pfr::for_each_field(t, std::forward(f)); +#else + // should not happen + static_assert(internal::is_std_pair::value || internal::is_std_tuple::value); +#endif } } /// @brief The number of elements in a tuple-like type. -/// This works for `std::pair` and `std::tuple` as well as types that are reflectable with -/// [pfr](https://github.com/apolukhin/pfr_non_boost). +/// This works for `std::pair` and `std::tuple`. +/// If KaMPIng's reflection support is enabled, this also works with types that are reflectable with +/// [pfr](https://github.com/boostorg/pfr). template constexpr size_t tuple_size = [] { if constexpr (internal::is_std_pair::value) { @@ -356,7 +372,15 @@ constexpr size_t tuple_size = [] { } else if constexpr (internal::is_std_tuple::value) { return std::tuple_size_v; } else { - return pfr::tuple_size_v; +#ifdef KAMPING_ENABLE_REFLECTION + return boost::pfr::tuple_size_v; +#else + if constexpr (std::is_arithmetic_v) { + return 1; + } else { + return std::tuple_size_v; + } +#endif } }(); } // namespace internal diff --git a/tests/mpi_datatype_test.cpp b/tests/mpi_datatype_test.cpp index 91d7daa11..e0615bd03 100644 --- a/tests/mpi_datatype_test.cpp +++ b/tests/mpi_datatype_test.cpp @@ -320,6 +320,7 @@ TEST(MpiDataTypeTest, byte_serialized_type_works) { PMPI_Type_free(&byte_serialized_type); } +#ifdef KAMPING_ENABLE_REFLECTION TEST(MpiDataTypeTest, struct_type_works_with_struct) { struct TestStruct { uint8_t a; @@ -556,6 +557,7 @@ TEST(MpiDataTypeTest, struct_type_works_with_nested_struct) { EXPECT_EQ(u.nested.d, t.nested.d); PMPI_Type_free(&struct_type); } +#endif TEST(MpiDataTypeTest, struct_type_works_with_pair) { MPI_Datatype resized_type = kamping::struct_type>::data_type();