From a8bd8bf2b7b81a4abaf134c039dcc8ca9f33b124 Mon Sep 17 00:00:00 2001 From: Ben Deane Date: Wed, 20 May 2026 10:45:51 -0600 Subject: [PATCH] :sparkles: Add `tuple::tail` Problem: - A useful algorithm pattern is zip-with-tail. There is currently no easy way to get a tuple's tail. Solution: - Add `tuple::tail`. --- docs/tuple.adoc | 9 +++++++++ include/stdx/tuple.hpp | 27 +++++++++++++++++++++++++++ test/tuple.cpp | 15 +++++++++++++++ test/tuple_algorithms.cpp | 5 +++++ 4 files changed, 56 insertions(+) diff --git a/docs/tuple.adoc b/docs/tuple.adoc index 2e0de8e..4abf862 100644 --- a/docs/tuple.adoc +++ b/docs/tuple.adoc @@ -145,6 +145,15 @@ auto sum1 = stdx::tuple{"hello"s, "world"s}.join( [] (auto const &acc, auto const &s) { return acc + ", " + s; }); // "hello, world" ---- +`tail` is a member function that returns the tail of a tuple, i.e. every element +from index 1 onwards. +[source,cpp] +---- +auto t = stdx::tuple{1, 2, 3}.tail(); // stdx::tuple{2, 3} +---- + +NOTE: `tail` called on an empty tuple returns an empty tuple. + === Indexed tuples Sometimes, it is useful to index a tuple by something other than a plain diff --git a/include/stdx/tuple.hpp b/include/stdx/tuple.hpp index fc29f92..213c3d8 100644 --- a/include/stdx/tuple.hpp +++ b/include/stdx/tuple.hpp @@ -335,6 +335,33 @@ struct tuple_impl, index_function_list, Ts...> constexpr static auto size = std::integral_constant{}; + constexpr static auto empty = std::bool_constant{}; + + [[nodiscard]] constexpr auto tail() const & { + if constexpr (empty()) { + return *this; + } else { + return [&](std::index_sequence) { + return tuple_impl, + index_function_list, + nth_t...>{ + (*this)[index]...}; + }(std::make_index_sequence{}); + } + } + + [[nodiscard]] constexpr auto tail() && { + if constexpr (empty()) { + return *this; + } else { + return [&](std::index_sequence) { + return tuple_impl, + index_function_list, + nth_t...>{ + std::move(*this)[index]...}; + }(std::make_index_sequence{}); + } + } private: template diff --git a/test/tuple.cpp b/test/tuple.cpp index 9047510..32d422c 100644 --- a/test/tuple.cpp +++ b/test/tuple.cpp @@ -492,3 +492,18 @@ TEST_CASE("indexing unambiguously", "[tuple]") { STATIC_CHECK(t[0_idx] == 42); STATIC_CHECK(t[1_idx][0_idx] == 17); } + +TEST_CASE("tuple tail (const lvalue)", "[tuple]") { + constexpr auto t = stdx::tuple{1, 2, 3}; + STATIC_CHECK(t.tail() == stdx::tuple{2, 3}); +} + +TEST_CASE("tuple tail (rvalue)", "[tuple]") { + auto t = stdx::tuple{move_only{42}, move_only{17}}; + CHECK(std::move(t).tail() == stdx::tuple{move_only{17}}); +} + +TEST_CASE("tuple tail (empty tuple)", "[tuple]") { + constexpr auto t = stdx::tuple{}; + STATIC_CHECK(t.tail() == stdx::tuple{}); +} diff --git a/test/tuple_algorithms.cpp b/test/tuple_algorithms.cpp index 7f5523c..ab97f15 100644 --- a/test/tuple_algorithms.cpp +++ b/test/tuple_algorithms.cpp @@ -63,6 +63,11 @@ TEST_CASE("transform stops at smallest tuple length", "[tuple_algorithms]") { stdx::tuple{1, 2}) == stdx::tuple{2, 4}); } +TEST_CASE("zip with tail", "[tuple_algorithms]") { + auto t = stdx::tuple{1, 2, 3}; + CHECK(stdx::transform(std::plus{}, t, t.tail()) == stdx::tuple{3, 5}); +} + namespace { template struct map_entry { using key_t = Key;