Compile-Time Tuple Utilities
Here we have introduced two new useful functions:
- peel_front
- print_tuple
- tuple_split
These basic utilities allow us to perform compile time type manipulations and extend the stl.
peel_front will remove a single element from the front of a tuple and return it.
tuple_split takes either a type parameter to split on or a list of indexes to split on.
print_tuple takes an abitrary tuple and prints it.
template <typename... TYPES, typename INTTYPE, INTTYPE... Idxs>
constexpr auto peel_front(std::tuple<TYPES...> tuple, std::integer_sequence<INTTYPE, Idxs...>) {
return std::make_tuple(std::get<Idxs + 1>(tuple)...);
}
template <typename FIRST, typename... TYPES>
constexpr std::tuple<TYPES...> peel_front(std::tuple<FIRST, TYPES...> val) {
return peel_front(val, std::make_integer_sequence<int, sizeof...(TYPES)>());
}
namespace _detail {
struct tag{};
template <typename TYPE_TO_SPLIT_ON, typename... TYPES, typename... RESULT_TYPES>
constexpr auto tuple_split(std::tuple<TYPES...> so_far, std::tuple<RESULT_TYPES...> result, std::tuple<>) {
return std::tuple_cat(so_far, std::make_tuple(result));
}
template <typename TYPE_TO_SPLIT_ON, typename FIRST, typename... TYPES, typename... TYPES2, typename... RESULT_TYPES>
constexpr auto tuple_split(std::tuple<TYPES...> so_far, std::tuple<RESULT_TYPES...> result, std::tuple<FIRST, TYPES2...> to_process) {
if constexpr(std::is_same_v<FIRST, TYPE_TO_SPLIT_ON>) {
return tuple_split<TYPE_TO_SPLIT_ON>(std::tuple_cat(so_far, std::make_tuple(result)), std::tuple<>{}, peel_front(to_process));
}
else {
return tuple_split<TYPE_TO_SPLIT_ON>(so_far, std::tuple_cat(result, std::make_tuple(std::get<0>(to_process))), peel_front(to_process));
}
}
template <typename INTTYPE, INTTYPE... IdxsLeft, INTTYPE... IdxsRight, typename... TYPES>
constexpr auto tuple_split(std::tuple<TYPES...> tup, std::integer_sequence<INTTYPE, IdxsLeft...>, std::integer_sequence<INTTYPE, IdxsRight...>) {
std::cout << (( std::to_string(IdxsRight) + " " ) + ...) << std::endl;
return std::make_tuple(std::make_tuple(std::get<IdxsLeft>(tup)...), std::make_tuple(std::get<IdxsRight + sizeof...(IdxsLeft) + 1>(tup)...));
}
template <int FIRST, typename... TYPES>
constexpr auto tuple_split(std::tuple<TYPES...> tup, tag) {
const auto& [left, right] = tuple_split(tup, std::make_integer_sequence<int, FIRST - 1>(), std::make_integer_sequence<int, sizeof...(TYPES) - FIRST>());
return std::tuple_cat(std::make_tuple(left), std::make_tuple(right));
}
template <int FIRST, int ...REST, typename... TYPES, typename = std::enable_if_t<(sizeof...(REST) > 0)>>
constexpr auto tuple_split(std::tuple<TYPES...> tup, tag) {
static_assert(sizeof...(REST) > 0, "Must have at least one element in the parameter pack!");
const auto& [left, right] = tuple_split(tup, std::make_integer_sequence<int, FIRST - 1>(), std::make_integer_sequence<int, sizeof...(TYPES) - FIRST>());
return std::tuple_cat(std::make_tuple(left), tuple_split<(REST - FIRST)...>(right, tag{}));
}
}
namespace std{
template <typename TYPE_TO_SPLIT_ON, typename... TYPES>
constexpr auto tuple_split(std::tuple<TYPES...> tuple) {
return _detail::tuple_split<TYPE_TO_SPLIT_ON>(std::tuple<>{}, std::tuple<>{}, tuple);
}
template <int ...Idxs, typename... TYPES>
constexpr auto tuple_split(std::tuple<TYPES...> tuple) {
static_assert((( Idxs >= 0) && ...), "All indexes should be positive!");
return _detail::tuple_split<Idxs...>(tuple, _detail::tag{});
}
}
template <typename... TYPES, typename INTTYPE, INTTYPE ...Idxs>
inline std::ostream& print_tuple(std::ostream& os, std::tuple<TYPES...> tup, std::integer_sequence<INTTYPE, Idxs...>) {
os << "std::tuple<";
int dummy1[] = { ( (os << (Idxs == 0 ? "" : ", ") << typeid(std::get<Idxs>(tup)).name()) ,0)... };
os << ">(";
int dummy2[] = { ( (os << (Idxs == 0 ? "" : ", ") << std::get<Idxs>(tup)),0)... };
os << ")";
return os;
}
template <typename... TYPES>
inline std::ostream& operator<<(std::ostream& os, std::tuple<TYPES...> tup) {
return print_tuple(os, tup, std::make_integer_sequence<int, sizeof...(TYPES)>());
}
// TODO: Replace examples and use TDD by writing your own tests
Describe(any_group_name_you_want)
{
It(Can_Perform_split_on_type_params)
{
auto tup = std::make_tuple(1,2,3,4l,3,4,5,6l,7);
const auto& [first, second, third] = std::tuple_split<long>(tup);
Assert::That(std::get<0>(first), Equals(1));
Assert::That(std::get<1>(first), Equals(2));
Assert::That(std::get<2>(first), Equals(3));
Assert::That(std::get<0>(second), Equals(3));
Assert::That(std::get<1>(second), Equals(4));
Assert::That(std::get<2>(second), Equals(5));
Assert::That(std::get<0>(third), Equals(7));
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << third << std::endl;
}
It(Can_perform_split_on_single_int_param)
{
auto tup = std::make_tuple(1,2,3,4l,3,4,5,6l,7);
const auto& [first, second, third] = std::tuple_split<2,4>(tup);
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << third << std::endl;
}
};