Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add split_tuple function #5613

Merged
merged 2 commits into from
Nov 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Utilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ spectre_target_headers(
Requires.hpp
SetNumberOfGridPoints.hpp
Spherepack.hpp
SplitTuple.hpp
StaticCache.hpp
StdArrayHelpers.hpp
StdHelpers.hpp
Expand Down
2 changes: 1 addition & 1 deletion src/Utilities/Gsl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,6 @@ std::ostream& operator<<(std::ostream& os, const span<ElementType, Extent> t) {
/// \note This is not a standard GSL function, and so is not in the
/// gsl namespace.
template <typename T>
gsl::not_null<T*> make_not_null(T* ptr) {
constexpr gsl::not_null<T*> make_not_null(T* ptr) {
return gsl::not_null<T*>(ptr);
}
73 changes: 73 additions & 0 deletions src/Utilities/SplitTuple.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <cstddef>
#include <tuple>
#include <utility>

#include "Utilities/Gsl.hpp"
#include "Utilities/TMPL.hpp"

namespace split_tuple_detail {
template <size_t Offset, size_t... Indices, typename... TupleTypes>
constexpr std::tuple<
std::tuple_element_t<Offset + Indices, std::tuple<TupleTypes...>>...>
extract_subset(
std::index_sequence<Indices...> /*meta*/,
[[maybe_unused]] const gsl::not_null<std::tuple<TupleTypes...>*> tuple) {
return {std::forward<
std::tuple_element_t<Offset + Indices, std::tuple<TupleTypes...>>>(
std::get<Offset + Indices>(*tuple))...};
}

template <typename... Offsets, typename... Sizes, typename... TupleTypes>
constexpr auto impl(tmpl::list<Offsets...> /*meta*/,
tmpl::list<Sizes...> /*meta*/,
[[maybe_unused]] std::tuple<TupleTypes...> tuple) {
static_assert((0 + ... + Sizes::value) == sizeof...(TupleTypes),
"Tuple size does not match output sizes.");
return std::make_tuple(extract_subset<Offsets::value>(
std::make_index_sequence<Sizes::value>{}, make_not_null(&tuple))...);
}
} // namespace split_tuple_detail

/// \ingroup UtilitiesGroup
/// Split a `std::tuple` into multiple tuples
///
/// \note There are two functions with this name, but doxygen can't
/// figure that out. They have signatures
/// ```
/// template <typename SizeList, typename... TupleTypes>
/// constexpr auto split_tuple(std::tuple<TupleTypes...> tuple);
/// template <size_t... Sizes, typename... TupleTypes>
/// constexpr auto split_tuple(std::tuple<TupleTypes...> tuple);
/// ```
///
/// Given a list of sizes, either directly as template parameters or
/// as a typelist of integral constant types, split the passed tuple
/// into pieces containing the specified number of entries. The
/// passed sizes must sum to the size of the tuple.
///
/// \returns a `std::tuple` of `std::tuple`s
///
/// \see std::tuple_cat for the inverse operation.
///
/// \snippet Utilities/Test_SplitTuple.cpp split_tuple
/// @{
template <typename SizeList, typename... TupleTypes>
constexpr auto split_tuple(std::tuple<TupleTypes...> tuple) {
using offsets = tmpl::pop_back<
tmpl::fold<SizeList, tmpl::list<tmpl::size_t<0>>,
tmpl::bind<tmpl::push_back, tmpl::_state,
tmpl::plus<tmpl::bind<tmpl::back, tmpl::_state>,
tmpl::_element>>>>;
return split_tuple_detail::impl(offsets{}, SizeList{}, std::move(tuple));
}

template <size_t... Sizes, typename... TupleTypes>
constexpr auto split_tuple(std::tuple<TupleTypes...> tuple) {
return split_tuple<tmpl::list<tmpl::size_t<Sizes>...>>(std::move(tuple));
}
/// @}
1 change: 1 addition & 0 deletions tests/Unit/Utilities/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(LIBRARY_SOURCES
Test_Registration.cpp
Test_Requires.cpp
Test_SetNumberOfGridPoints.cpp
Test_SplitTuple.cpp
Test_StaticCache.cpp
Test_StdArrayHelpers.cpp
Test_StdHelpers.cpp
Expand Down
91 changes: 91 additions & 0 deletions tests/Unit/Utilities/Test_SplitTuple.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Distributed under the MIT License.
// See LICENSE.txt for details.

#include "Framework/TestingFramework.hpp"

#include <memory>
#include <tuple>
#include <type_traits>
#include <utility>

#include "Utilities/SplitTuple.hpp"
#include "Utilities/TMPL.hpp"

namespace {
// Test constexpr
// [split_tuple]
static_assert(split_tuple<2, 1>(std::tuple{1, 2, 3}) ==
std::tuple{std::tuple{1, 2}, std::tuple{3}});
static_assert(split_tuple<tmpl::integral_list<size_t, 2, 1>>(std::tuple{
1, 2, 3}) == std::tuple{std::tuple{1, 2}, std::tuple{3}});
// [split_tuple]

// Needed for type-deduction of the nested tuples.
template <typename... T>
std::tuple<T...> construct_tuple(T... args) {
return {std::move(args)...};
}

template <size_t... Sizes, typename... T, typename Expected>
void check(std::tuple<T...> tuple, const Expected& expected) {
auto split = split_tuple<Sizes...>(tuple);
static_assert(std::is_same_v<decltype(split), Expected>);
auto split_typelist =
split_tuple<tmpl::integral_list<size_t, Sizes...>>(tuple);
static_assert(std::is_same_v<decltype(split_typelist), Expected>);
CHECK(split == expected);
CHECK(split_typelist == expected);
}
} // namespace

SPECTRE_TEST_CASE("Unit.Utilities.SplitTuple", "[Unit][Utilities]") {
// Normal case
check<2, 1>(construct_tuple<int, double, float>(1, 2.0, 3.0),
construct_tuple<std::tuple<int, double>, std::tuple<float>>(
{1, 2.0}, {3.0}));
// Empty piece
check<2, 0, 1>(
construct_tuple<int, double, float>(1, 2.0, 3.0),
construct_tuple<std::tuple<int, double>, std::tuple<>, std::tuple<float>>(
{1, 2.0}, {}, {3.0}));
// No output
check<>(construct_tuple<>(), construct_tuple<>());
// Single output
check<2>(construct_tuple<int, double>(1, 2.0),
construct_tuple<std::tuple<int, double>>({1, 2.0}));
// Non-copyable
{
using Expected =
std::tuple<std::tuple<int>, std::tuple<std::unique_ptr<double>>>;
auto split =
split_tuple<1, 1>(construct_tuple<int, std::unique_ptr<double>>(
1, std::make_unique<double>(2.0)));
static_assert(std::is_same_v<decltype(split), Expected>);
auto split_typelist = split_tuple<tmpl::integral_list<size_t, 1, 1>>(
construct_tuple<int, std::unique_ptr<double>>(
1, std::make_unique<double>(2.0)));
static_assert(std::is_same_v<decltype(split_typelist), Expected>);
CHECK(std::get<0>(std::get<0>(split)) == 1);
CHECK(*std::get<0>(std::get<1>(split)) == 2.0);
CHECK(std::get<0>(std::get<0>(split_typelist)) == 1);
CHECK(*std::get<0>(std::get<1>(split_typelist)) == 2.0);
}
// References
{
// NOLINTBEGIN(bugprone-use-after-move)
const int a = 0;
double b = 0.0;
using Expected = std::tuple<std::tuple<const int&>, std::tuple<double&&>>;
auto split = split_tuple<1, 1>(
construct_tuple<const int&, double&&>(a, std::move(b)));
static_assert(std::is_same_v<decltype(split), Expected>);
auto split_typelist = split_tuple<tmpl::integral_list<size_t, 1, 1>>(
construct_tuple<const int&, double&&>(a, std::move(b)));
static_assert(std::is_same_v<decltype(split_typelist), Expected>);
CHECK(&std::get<0>(std::get<0>(split)) == &a);
CHECK(&std::get<0>(std::get<1>(split)) == &b);
CHECK(&std::get<0>(std::get<0>(split_typelist)) == &a);
CHECK(&std::get<0>(std::get<1>(split_typelist)) == &b);
// NOLINTEND(bugprone-use-after-move)
}
}
Loading