Skip to content

Commit

Permalink
Merge pull request #5613 from wthrowe/split_tuple
Browse files Browse the repository at this point in the history
Add split_tuple function
  • Loading branch information
nilsdeppe authored Nov 5, 2023
2 parents 52469ba + 363ae1a commit 4523a6c
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 1 deletion.
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)
}
}

0 comments on commit 4523a6c

Please sign in to comment.