From b8fe6528c83ff1076c7b0cf1f2078b6f0e184d2f Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 16 Jan 2026 20:53:43 +0100 Subject: [PATCH 01/18] Initial impl --- include/boost/redis/impl/flat_tree.ipp | 9 ++++++++ include/boost/redis/resp3/flat_tree.hpp | 28 +++++++++++++++---------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/include/boost/redis/impl/flat_tree.ipp b/include/boost/redis/impl/flat_tree.ipp index 7ee1aac4..17621bfe 100644 --- a/include/boost/redis/impl/flat_tree.ipp +++ b/include/boost/redis/impl/flat_tree.ipp @@ -11,10 +11,12 @@ #include #include +#include #include #include #include +#include #include namespace boost::redis::resp3 { @@ -228,6 +230,13 @@ void flat_tree::notify_done() data_tmp_offset_ = data_.size; } +const node_view& flat_tree::at(std::size_t i) const +{ + if (i >= size()) + BOOST_THROW_EXCEPTION(std::out_of_range("flat_tree::at")); + return view_tree_[i]; +} + bool operator==(flat_tree const& a, flat_tree const& b) { // data is already taken into account by comparing the nodes. diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index 4c6158c5..959a8990 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -15,6 +15,7 @@ #include #include +#include #include namespace boost::redis { @@ -54,6 +55,9 @@ struct flat_buffer { */ class flat_tree { public: + using iterator = const node_view*; + using reverse_iterator = std::reverse_iterator; + /** * @brief Default constructor. * @@ -117,6 +121,18 @@ class flat_tree { */ flat_tree& operator=(const flat_tree& other); + iterator begin() const noexcept { return data(); } + iterator end() const noexcept { return data() + size(); } + reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } + reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } + const node_view& at(std::size_t i) const; + const node_view& operator[](std::size_t i) const noexcept { return get_view()[i]; } + const node_view& front() const noexcept { return get_view().front(); } + const node_view& back() const noexcept { return get_view().back(); } + const node_view* data() const noexcept { return view_tree_.data(); } + bool empty() const noexcept { return size() != 0u; } + std::size_t size() const noexcept { return node_tmp_offset_; } + friend bool operator==(flat_tree const&, flat_tree const&); friend bool operator!=(flat_tree const&, flat_tree const&); @@ -189,17 +205,6 @@ class flat_tree { */ auto data_capacity() const noexcept -> std::size_t { return data_.capacity; } - /** @brief Returns a vector with the nodes in the tree. - * - * This is the main way to access the contents of the tree. - * - * @par Exception safety - * No-throw guarantee. - * - * @returns The nodes in the tree. - */ - span get_view() const noexcept { return {view_tree_.data(), node_tmp_offset_}; } - /** @brief Returns the number of memory reallocations that took place in the data buffer. * * This function returns how many reallocations in the data buffer were performed and @@ -226,6 +231,7 @@ class flat_tree { private: template friend class adapter::detail::general_aggregate; + span get_view() const noexcept { return {data(), size()}; } void notify_init(); void notify_done(); From c3d819d14705254b87a068ab0e9e224d7df9f7d1 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Fri, 16 Jan 2026 20:57:13 +0100 Subject: [PATCH 02/18] don't use the private api --- test/test_flat_tree.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 87b7f804..bba5ff75 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -70,11 +70,7 @@ void check_nodes( boost::span expected, boost::source_location loc = BOOST_CURRENT_LOCATION) { - if (!BOOST_TEST_ALL_EQ( - tree.get_view().begin(), - tree.get_view().end(), - expected.begin(), - expected.end())) + if (!BOOST_TEST_ALL_EQ(tree.begin(), tree.end(), expected.begin(), expected.end())) std::cerr << "Called from " << loc << std::endl; } From bee231e49456fdcb289edf13773d44866066fcde Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 14:20:46 +0100 Subject: [PATCH 03/18] Begin docs --- include/boost/redis/resp3/flat_tree.hpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index 959a8990..3a0f5b77 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -74,7 +74,7 @@ class flat_tree { * Constructs a tree by taking ownership of the nodes in `other`. * * @par Object lifetimes - * References to the nodes and strings in `other` remain valid. + * Iterators, pointers and references to the nodes and strings in `other` remain valid. * * @par Exception safety * No-throw guarantee. @@ -99,8 +99,8 @@ class flat_tree { * `other` is left in a valid but unspecified state. * * @par Object lifetimes - * References to the nodes and strings in `other` remain valid. - * References to the nodes and strings in `*this` are invalidated. + * Iterators, pointers and references to the nodes and strings in `other` remain valid. + * Iterators, pointers and references to the nodes and strings in `*this` are invalidated. * * @par Exception safety * No-throw guarantee. @@ -114,14 +114,21 @@ class flat_tree { * After the copy, `*this` and `other` have independent lifetimes (usual copy semantics). * * @par Object lifetimes - * References to the nodes and strings in `*this` are invalidated. + * Iterators, pointers and references to the nodes and strings in `*this` are invalidated. * * @par Exception safety * Basic guarantee. Memory allocations might throw. */ flat_tree& operator=(const flat_tree& other); + /** + * @brief Returns an iterator to the beginning of the range. + * + * @par Exception safety + * No-throw guarantee. + */ iterator begin() const noexcept { return data(); } + iterator end() const noexcept { return data() + size(); } reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } From 3cc147df35539c5657b3778c04af6a5864a45924 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 14:27:39 +0100 Subject: [PATCH 04/18] Reference docs for the rest of the members --- include/boost/redis/resp3/flat_tree.hpp | 100 +++++++++++++++++++++++- 1 file changed, 99 insertions(+), 1 deletion(-) diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index 3a0f5b77..2fcfe1e1 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -122,22 +122,120 @@ class flat_tree { flat_tree& operator=(const flat_tree& other); /** - * @brief Returns an iterator to the beginning of the range. + * @brief Returns an iterator to the beginning of the node range. * * @par Exception safety * No-throw guarantee. */ iterator begin() const noexcept { return data(); } + /** + * @brief Returns an iterator to the end of the node range. + * + * @par Exception safety + * No-throw guarantee. + */ iterator end() const noexcept { return data() + size(); } + + /** + * @brief Returns an iterator to the beginning of the reversed node range. + * + * Allows iterating the range of nodes in reverse order. + * + * @par Exception safety + * No-throw guarantee. + */ reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } + + /** + * @brief Returns an iterator to the beginning of the reversed node range. + * + * Allows iterating the range of nodes in reverse order. + * + * @par Exception safety + * No-throw guarantee. + */ reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } + + /** + * @brief Returns a reference to the node at the specified position (checked access). + * + * @par Exception safety + * Strong guarantee. Throws `std::out_of_range` if `i >= size()`. + * + * @param i Position of the node to return. + * @returns A reference to the node at position `i`. + */ const node_view& at(std::size_t i) const; + + /** + * @brief Returns a reference to the node at the specified position (unchecked access). + * + * @par Precondition + * `i < size()`. + * + * @par Exception safety + * No-throw guarantee. + * + * @param i Position of the node to return. + * @returns A reference to the node at position `i`. + */ const node_view& operator[](std::size_t i) const noexcept { return get_view()[i]; } + + /** + * @brief Returns a reference to the first node. + * + * @par Precondition + * `!empty()`. + * + * @par Exception safety + * No-throw guarantee. + * + * @returns A reference to the first node. + */ const node_view& front() const noexcept { return get_view().front(); } + + /** + * @brief Returns a reference to the last node. + * + * @par Precondition + * `!empty()`. + * + * @par Exception safety + * No-throw guarantee. + * + * @returns A reference to the last node. + */ const node_view& back() const noexcept { return get_view().back(); } + + /** + * @brief Returns a pointer to the underlying node storage. + * + * @par Exception safety + * No-throw guarantee. + * + * @returns A pointer to the underlying node array. + */ const node_view* data() const noexcept { return view_tree_.data(); } + + /** + * @brief Checks whether the tree is empty. + * + * @par Exception safety + * No-throw guarantee. + * + * @returns `true` if the tree contains no nodes, `false` otherwise. + */ bool empty() const noexcept { return size() != 0u; } + + /** + * @brief Returns the number of nodes in the tree. + * + * @par Exception safety + * No-throw guarantee. + * + * @returns The number of nodes. + */ std::size_t size() const noexcept { return node_tmp_offset_; } friend bool operator==(flat_tree const&, flat_tree const&); From fcce8f61ef138b7e6a2b5a6f59d58074ead55bad Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 14:33:41 +0100 Subject: [PATCH 05/18] fix iterator docs --- include/boost/redis/resp3/flat_tree.hpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index 2fcfe1e1..00d4a8e7 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -122,38 +122,46 @@ class flat_tree { flat_tree& operator=(const flat_tree& other); /** - * @brief Returns an iterator to the beginning of the node range. + * @brief Returns an iterator to the first element of the node range. * * @par Exception safety * No-throw guarantee. + * + * @returns An iterator to the first node. */ iterator begin() const noexcept { return data(); } /** - * @brief Returns an iterator to the end of the node range. + * @brief Returns an iterator past the last element in the node range. * * @par Exception safety * No-throw guarantee. + * + * @returns An iterator past the last element in the node range. */ iterator end() const noexcept { return data() + size(); } /** - * @brief Returns an iterator to the beginning of the reversed node range. + * @brief Returns an iterator to the first element of the reversed node range. * * Allows iterating the range of nodes in reverse order. * * @par Exception safety * No-throw guarantee. + * + * @returns An iterator to the first node of the reversed range. */ reverse_iterator rbegin() const noexcept { return reverse_iterator{end()}; } /** - * @brief Returns an iterator to the beginning of the reversed node range. + * @brief Returns an iterator past the last element of the reversed node range. * * Allows iterating the range of nodes in reverse order. * * @par Exception safety * No-throw guarantee. + * + * @returns An iterator past the last element of the reversed node range. */ reverse_iterator rend() const noexcept { return reverse_iterator{begin()}; } From a017c740b90f1b446a40531913ec1979275f4583 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 14:36:21 +0100 Subject: [PATCH 06/18] Iterator docs --- include/boost/redis/resp3/flat_tree.hpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index 00d4a8e7..f9d5da0b 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -55,7 +55,20 @@ struct flat_buffer { */ class flat_tree { public: + /** + * @brief The type of the iterators returned by @ref begin and @ref end. + * + * It is guaranteed to be a contiguous iterator. While this is currently a pointer, + * users shouldn't rely on this fact, as the exact implementation may change between releases. + */ using iterator = const node_view*; + + /** + * @brief The type of the iterators returned by @ref rbegin and @ref rend. + * + * As with @ref iterator, users should treat this type as an unspecified + * contiguous iterator type rather than assuming a specific type. + */ using reverse_iterator = std::reverse_iterator; /** From 5ef45d95778edec4bb285a2871444135508f9abc Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 14:43:31 +0100 Subject: [PATCH 07/18] test iterators --- test/test_flat_tree.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index bba5ff75..033ec7e9 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -17,6 +17,7 @@ #include "print_node.hpp" #include +#include #include #include #include @@ -1086,6 +1087,37 @@ void test_move_assign_tmp() BOOST_TEST_EQ(t.get_total_msgs(), 2u); } +// --- Iterators --- +// We can obtain iterators using begin() and end() and use them to iterate +void test_iterators() +{ + // Setup + flat_tree t; + add_nodes(t, "+node1\r\n"); + add_nodes(t, ":200\r\n"); + constexpr node_view node1{type::simple_string, 1u, 0u, "node1"}; + constexpr node_view node2{type::number, 1u, 0u, "200"}; + + // These methods are const + const auto& tconst = t; + auto it = tconst.begin(); + auto end = tconst.end(); + + // Iteration using iterators + BOOST_TEST_NE(it, end); + BOOST_TEST_EQ(*it, node1); + BOOST_TEST_NE(++it, end); + BOOST_TEST_EQ(*it, node2); + BOOST_TEST_EQ(++it, end); + + // Iteration using range for + std::vector nodes; + for (const auto& n : t) + nodes.push_back(n); + constexpr std::array expected_nodes{node1, node2}; + BOOST_TEST_ALL_EQ(nodes.begin(), nodes.end(), expected_nodes.begin(), expected_nodes.end()); +} + // --- Comparison --- void test_comparison_different() { @@ -1306,6 +1338,8 @@ int main() test_move_assign_both_empty(); test_move_assign_tmp(); + test_iterators(); + test_comparison_different(); test_comparison_different_node_types(); test_comparison_equal(); From bf76a7208e362adb04e5216c5f7fc966e72d650c Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 14:49:40 +0100 Subject: [PATCH 08/18] iterator empty and tmp --- test/test_flat_tree.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 033ec7e9..474d36ab 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -1118,6 +1118,23 @@ void test_iterators() BOOST_TEST_ALL_EQ(nodes.begin(), nodes.end(), expected_nodes.begin(), expected_nodes.end()); } +// Empty ranges don't cause trouble +void test_iterators_empty() +{ + flat_tree t; + BOOST_TEST_EQ(t.begin(), t.end()); +} + +// Tmp area is not included in the range +// More or less tested with the add_nodes tests +void test_iterators_tmp() +{ + parser p; + flat_tree t; + BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n")); + BOOST_TEST_EQ(t.begin(), t.end()); +} + // --- Comparison --- void test_comparison_different() { @@ -1339,6 +1356,8 @@ int main() test_move_assign_tmp(); test_iterators(); + test_iterators_empty(); + test_iterators_tmp(); test_comparison_different(); test_comparison_different_node_types(); From 988892e29633ebce7dd3bb800f6c2b0e41cf0081 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:01:14 +0100 Subject: [PATCH 09/18] concept checks --- test/test_flat_tree.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 474d36ab..1dbe1e34 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -11,6 +11,7 @@ #include #include +#include // for a safe #include #include #include @@ -20,11 +21,17 @@ #include #include #include +#include #include #include #include #include +#if (__cpp_lib_ranges >= 201911L) && (__cpp_lib_concepts >= 202002L) +#define BOOST_REDIS_TEST_RANGE_CONCEPTS +#include +#endif + using boost::redis::adapter::adapt2; using boost::redis::adapter::result; using boost::redis::resp3::tree; @@ -1135,6 +1142,16 @@ void test_iterators_tmp() BOOST_TEST_EQ(t.begin(), t.end()); } +// The iterator should be contiguous +#ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS +static_assert(std::contiguous_iterator); +#endif + +// The range should model contiguous range +#ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS +static_assert(std::ranges::contiguous_range); +#endif + // --- Comparison --- void test_comparison_different() { From 5c3044f9ff4a95cd416134fc60a6871273398c18 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:08:18 +0100 Subject: [PATCH 10/18] reverse iterators --- test/test_flat_tree.cpp | 52 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 1dbe1e34..1cc31f81 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -1147,6 +1147,54 @@ void test_iterators_tmp() static_assert(std::contiguous_iterator); #endif +// --- Reverse iterators --- +// We can obtain iterators using rbegin() and rend() and use them to iterate +void test_reverse_iterators() +{ + // Setup + flat_tree t; + add_nodes(t, "+node1\r\n"); + add_nodes(t, ":200\r\n"); + + // These methods are const + const auto& tconst = t; + + constexpr node_view expected_nodes[] = { + {type::number, 1u, 0u, "200" }, + {type::simple_string, 1u, 0u, "node1"}, + }; + BOOST_TEST_ALL_EQ( + tconst.rbegin(), + tconst.rend(), + std::begin(expected_nodes), + std::end(expected_nodes)); +} + +// Empty ranges don't cause trouble +void test_reverse_iterators_empty() +{ + flat_tree t; + BOOST_TEST(t.rbegin() == t.rend()); +} + +// Tmp area is not included in the range +void test_reverse_iterators_tmp() +{ + parser p; + flat_tree t; + + // Add one full message and a partial one + add_nodes(t, "*1\r\n+node1\r\n"); + BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n")); + + // Only the full message appears in the reversed range + constexpr node_view expected_nodes[] = { + {type::simple_string, 1u, 1u, "node1"}, + {type::array, 1u, 0u, "" }, + }; + BOOST_TEST_ALL_EQ(t.rbegin(), t.rend(), std::begin(expected_nodes), std::end(expected_nodes)); +} + // The range should model contiguous range #ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS static_assert(std::ranges::contiguous_range); @@ -1376,6 +1424,10 @@ int main() test_iterators_empty(); test_iterators_tmp(); + test_reverse_iterators(); + test_reverse_iterators_empty(); + test_reverse_iterators_tmp(); + test_comparison_different(); test_comparison_different_node_types(); test_comparison_equal(); From 93711ed9eb081762c43f2f3a8f1d7dd81b667b1b Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:12:54 +0100 Subject: [PATCH 11/18] at --- test/test_flat_tree.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 1cc31f81..1ebf1d55 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -1195,6 +1197,41 @@ void test_reverse_iterators_tmp() BOOST_TEST_ALL_EQ(t.rbegin(), t.rend(), std::begin(expected_nodes), std::end(expected_nodes)); } +// --- at --- +void test_at() +{ + parser p; + flat_tree t; + + // Add one full message and a partial one + add_nodes(t, "*1\r\n+node1\r\n"); + BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n")); + + // Nodes in the range can be accessed with at() + constexpr node_view n0{type::array, 1u, 0u, ""}; + constexpr node_view n1{type::simple_string, 1u, 1u, "node1"}; + BOOST_TEST_EQ(t.at(0u), n0); + BOOST_TEST_EQ(t.at(1u), n1); + + // Nodes in the tmp area are not considered in range + BOOST_TEST_THROWS(t.at(2u), std::out_of_range); + BOOST_TEST_THROWS(t.at(3u), std::out_of_range); + + // Indices out of range throw + BOOST_TEST_THROWS(t.at(4u), std::out_of_range); + BOOST_TEST_THROWS(t.at(5u), std::out_of_range); + BOOST_TEST_THROWS(t.at((std::numeric_limits::max)()), std::out_of_range); +} + +// Empty ranges don't cause trouble +void test_at_empty() +{ + flat_tree t; + BOOST_TEST_THROWS(t.at(0u), std::out_of_range); + BOOST_TEST_THROWS(t.at(2u), std::out_of_range); + BOOST_TEST_THROWS(t.at((std::numeric_limits::max)()), std::out_of_range); +} + // The range should model contiguous range #ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS static_assert(std::ranges::contiguous_range); @@ -1428,6 +1465,9 @@ int main() test_reverse_iterators_empty(); test_reverse_iterators_tmp(); + test_at(); + test_at_empty(); + test_comparison_different(); test_comparison_different_node_types(); test_comparison_equal(); From c775c59993f703c21e91d8acd6b5892e41a7d876 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:24:48 +0100 Subject: [PATCH 12/18] operator[] --- test/test_flat_tree.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 1ebf1d55..39393aee 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -1237,6 +1237,18 @@ void test_at_empty() static_assert(std::ranges::contiguous_range); #endif +// --- operator[] --- +void test_operator_subscript() +{ + flat_tree t; + add_nodes(t, "*1\r\n+node1\r\n"); + + constexpr node_view n0{type::array, 1u, 0u, ""}; + constexpr node_view n1{type::simple_string, 1u, 1u, "node1"}; + BOOST_TEST_EQ(t[0u], n0); + BOOST_TEST_EQ(t[1u], n1); +} + // --- Comparison --- void test_comparison_different() { @@ -1468,6 +1480,8 @@ int main() test_at(); test_at_empty(); + test_operator_subscript(); + test_comparison_different(); test_comparison_different_node_types(); test_comparison_equal(); From 9ea57db47637e52e7b56639d5d40b0472d5bc73f Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:27:40 +0100 Subject: [PATCH 13/18] front and back --- test/test_flat_tree.cpp | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 39393aee..29c741b3 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -1232,23 +1232,31 @@ void test_at_empty() BOOST_TEST_THROWS(t.at((std::numeric_limits::max)()), std::out_of_range); } -// The range should model contiguous range -#ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS -static_assert(std::ranges::contiguous_range); -#endif - -// --- operator[] --- -void test_operator_subscript() +// --- operator[], front, back --- +void test_unchecked_access() { flat_tree t; - add_nodes(t, "*1\r\n+node1\r\n"); + add_nodes(t, "*2\r\n+node1\r\n+node2\r\n"); - constexpr node_view n0{type::array, 1u, 0u, ""}; + constexpr node_view n0{type::array, 2u, 0u, ""}; constexpr node_view n1{type::simple_string, 1u, 1u, "node1"}; + constexpr node_view n2{type::simple_string, 1u, 1u, "node2"}; + + // operator [] BOOST_TEST_EQ(t[0u], n0); BOOST_TEST_EQ(t[1u], n1); + BOOST_TEST_EQ(t[2u], n2); + + // Front and back + BOOST_TEST_EQ(t.front(), n0); + BOOST_TEST_EQ(t.back(), n2); } +// The range should model contiguous range +#ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS +static_assert(std::ranges::contiguous_range); +#endif + // --- Comparison --- void test_comparison_different() { @@ -1480,7 +1488,7 @@ int main() test_at(); test_at_empty(); - test_operator_subscript(); + test_unchecked_access(); test_comparison_different(); test_comparison_different_node_types(); From 8905c6da4840fa95af4a025ee1b0f177f5171d3e Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:30:35 +0100 Subject: [PATCH 14/18] data --- test/test_flat_tree.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 29c741b3..83912af2 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -1252,6 +1252,28 @@ void test_unchecked_access() BOOST_TEST_EQ(t.back(), n2); } +// --- data --- +void test_data() +{ + flat_tree t; + add_nodes(t, "*1\r\n+node1\r\n"); + + constexpr node_view expected_nodes[] = { + {type::array, 1u, 0u, "" }, + {type::simple_string, 1u, 1u, "node1"}, + }; + + BOOST_TEST_NE(t.data(), nullptr); + BOOST_TEST_ALL_EQ(t.data(), t.data() + 2u, std::begin(expected_nodes), std::end(expected_nodes)); +} + +// Empty ranges don't cause trouble +void test_data_empty() +{ + flat_tree t; + BOOST_TEST_EQ(t.data(), nullptr); +} + // The range should model contiguous range #ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS static_assert(std::ranges::contiguous_range); @@ -1490,6 +1512,9 @@ int main() test_unchecked_access(); + test_data(); + test_data_empty(); + test_comparison_different(); test_comparison_different_node_types(); test_comparison_equal(); From 17efcb5e3359e451815dfe93be5d38806d6bf51e Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:33:54 +0100 Subject: [PATCH 15/18] fix empty bug --- include/boost/redis/resp3/flat_tree.hpp | 2 +- test/test_flat_tree.cpp | 49 +++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index f9d5da0b..a536078d 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -247,7 +247,7 @@ class flat_tree { * * @returns `true` if the tree contains no nodes, `false` otherwise. */ - bool empty() const noexcept { return size() != 0u; } + bool empty() const noexcept { return size() == 0u; } /** * @brief Returns the number of nodes in the tree. diff --git a/test/test_flat_tree.cpp b/test/test_flat_tree.cpp index 83912af2..bc8a6545 100644 --- a/test/test_flat_tree.cpp +++ b/test/test_flat_tree.cpp @@ -1274,6 +1274,50 @@ void test_data_empty() BOOST_TEST_EQ(t.data(), nullptr); } +// --- size and empty --- +void test_size() +{ + flat_tree t; + add_nodes(t, "*1\r\n+node1\r\n"); + + BOOST_TEST_EQ(t.size(), 2u); + BOOST_TEST_NOT(t.empty()); +} + +void test_size_empty() +{ + flat_tree t; + + BOOST_TEST_EQ(t.size(), 0u); + BOOST_TEST(t.empty()); +} + +// Tmp area not taken into account +void test_size_tmp() +{ + parser p; + flat_tree t; + + // Add one full message and a partial one + add_nodes(t, "*1\r\n+node1\r\n"); + BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n")); + + BOOST_TEST_EQ(t.size(), 2u); + BOOST_TEST_NOT(t.empty()); +} + +void test_size_tmp_only() +{ + parser p; + flat_tree t; + + // Add one partial message + BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n")); + + BOOST_TEST_EQ(t.size(), 0u); + BOOST_TEST(t.empty()); +} + // The range should model contiguous range #ifdef BOOST_REDIS_TEST_RANGE_CONCEPTS static_assert(std::ranges::contiguous_range); @@ -1515,6 +1559,11 @@ int main() test_data(); test_data_empty(); + test_size(); + test_size_empty(); + test_size_tmp(); + test_size_tmp_only(); + test_comparison_different(); test_comparison_different_node_types(); test_comparison_equal(); From 79f286a0f63c996d426c7a55d907d36902de5090 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 15:50:48 +0100 Subject: [PATCH 16/18] Fix the other usages --- example/cpp20_chat_room.cpp | 2 +- example/cpp20_subscriber.cpp | 2 +- test/test_conn_exec.cpp | 2 +- test/test_conn_push2.cpp | 8 ++++---- test/test_generic_flat_response.cpp | 6 +----- 5 files changed, 8 insertions(+), 12 deletions(-) diff --git a/example/cpp20_chat_room.cpp b/example/cpp20_chat_room.cpp index 4dd8180d..de78102d 100644 --- a/example/cpp20_chat_room.cpp +++ b/example/cpp20_chat_room.cpp @@ -73,7 +73,7 @@ auto receiver(std::shared_ptr conn) -> awaitable // The response must be consumed without suspending the // coroutine i.e. without the use of async operations. - for (auto const& elem : resp.value().get_view()) + for (auto const& elem : resp.value()) std::cout << elem.value << "\n"; std::cout << std::endl; diff --git a/example/cpp20_subscriber.cpp b/example/cpp20_subscriber.cpp index b3eb9cdd..c335fe92 100644 --- a/example/cpp20_subscriber.cpp +++ b/example/cpp20_subscriber.cpp @@ -74,7 +74,7 @@ auto receiver(std::shared_ptr conn) -> asio::awaitable // The response must be consumed without suspending the // coroutine i.e. without the use of async operations. - for (auto const& elem : resp.value().get_view()) + for (auto const& elem : resp.value()) std::cout << elem.value << "\n"; std::cout << std::endl; diff --git a/test/test_conn_exec.cpp b/test/test_conn_exec.cpp index 22216f9b..715d844f 100644 --- a/test/test_conn_exec.cpp +++ b/test/test_conn_exec.cpp @@ -205,7 +205,7 @@ BOOST_AUTO_TEST_CASE(exec_generic_flat_response) BOOST_TEST_REQUIRE(finished); BOOST_TEST(resp.has_value()); - BOOST_TEST(resp->get_view().front().value == "PONG"); + BOOST_TEST(resp.value().front().value == "PONG"); } } // namespace diff --git a/test/test_conn_push2.cpp b/test/test_conn_push2.cpp index 5c260f8c..d83ecbe6 100644 --- a/test/test_conn_push2.cpp +++ b/test/test_conn_push2.cpp @@ -445,15 +445,15 @@ struct test_pubsub_state_restoration_impl { { // Checks for the expected subscriptions and patterns after restoration std::set seen_channels, seen_patterns; - for (auto it = resp_push.get_view().begin(); it != resp_push.get_view().end();) { + for (auto it = resp_push.begin(); it != resp_push.end();) { // The root element should be a push BOOST_TEST_EQ(it->data_type, type::push); BOOST_TEST_GE(it->aggregate_size, 2u); - BOOST_TEST(++it != resp_push.get_view().end()); + BOOST_TEST(++it != resp_push.end()); // The next element should be the message type std::string_view msg_type = it->value; - BOOST_TEST(++it != resp_push.get_view().end()); + BOOST_TEST(++it != resp_push.end()); // The next element is the channel or pattern if (msg_type == "subscribe") @@ -462,7 +462,7 @@ struct test_pubsub_state_restoration_impl { seen_patterns.insert(it->value); // Skip the rest of the nodes - while (it != resp_push.get_view().end() && it->depth != 0u) + while (it != resp_push.end() && it->depth != 0u) ++it; } diff --git a/test/test_generic_flat_response.cpp b/test/test_generic_flat_response.cpp index ff9e2b86..b667f5bd 100644 --- a/test/test_generic_flat_response.cpp +++ b/test/test_generic_flat_response.cpp @@ -36,11 +36,7 @@ void test_success() std::vector expected_nodes{ {type::simple_string, 1u, 0u, "hello"}, }; - BOOST_TEST_ALL_EQ( - resp->get_view().begin(), - resp->get_view().end(), - expected_nodes.begin(), - expected_nodes.end()); + BOOST_TEST_ALL_EQ(resp->begin(), resp->end(), expected_nodes.begin(), expected_nodes.end()); } // If an error of any kind appears, we set the overall result to error From 584f8f351de06e7502acacd913c4e9bc42eee395 Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 16:10:44 +0100 Subject: [PATCH 17/18] get_view cleanup and operator== impl cleanup --- README.md | 2 +- doc/modules/ROOT/pages/index.adoc | 2 +- include/boost/redis/impl/flat_tree.ipp | 7 ++----- include/boost/redis/resp3/flat_tree.hpp | 14 +++++++------- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 62a03896..312939b2 100644 --- a/README.md +++ b/README.md @@ -120,7 +120,7 @@ auto receiver(std::shared_ptr conn) -> asio::awaitable // The response must be consumed without suspending the // coroutine i.e. without the use of async operations. - for (auto const& elem : resp.value().get_view()) + for (auto const& elem : resp.value()) std::cout << elem.value << "\n"; std::cout << std::endl; diff --git a/doc/modules/ROOT/pages/index.adoc b/doc/modules/ROOT/pages/index.adoc index b7194d1b..9493799e 100644 --- a/doc/modules/ROOT/pages/index.adoc +++ b/doc/modules/ROOT/pages/index.adoc @@ -131,7 +131,7 @@ auto receiver(std::shared_ptr conn) -> asio::awaitable // The response must be consumed without suspending the // coroutine i.e. without the use of async operations. - for (auto const& elem : resp.value().get_view()) + for (auto const& elem : resp.value()) std::cout << elem.value << "\n"; std::cout << std::endl; diff --git a/include/boost/redis/impl/flat_tree.ipp b/include/boost/redis/impl/flat_tree.ipp index 17621bfe..a891f6c8 100644 --- a/include/boost/redis/impl/flat_tree.ipp +++ b/include/boost/redis/impl/flat_tree.ipp @@ -241,11 +241,8 @@ bool operator==(flat_tree const& a, flat_tree const& b) { // data is already taken into account by comparing the nodes. // Only committed nodes should be taken into account. - auto a_nodes = a.get_view(); - auto b_nodes = b.get_view(); - return a_nodes.size() == b_nodes.size() && - std::equal(a_nodes.begin(), a_nodes.end(), b_nodes.begin()) && - a.total_msgs_ == b.total_msgs_; + return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin()) && + a.get_total_msgs() == b.get_total_msgs(); } } // namespace boost::redis::resp3 diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index a536078d..d62a5fc3 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -46,8 +46,12 @@ struct flat_buffer { * to obtain how many responses this object contains. * * Objects are typically created by the user and passed to @ref connection::async_exec - * to be used as response containers. Call @ref get_view to access the actual RESP3 nodes. - * Once populated, `flat_tree` can't be modified, except for @ref clear and assignment. + * to be used as response containers. Once populated, they can be used as a const range + * of @ref resp3::node_view objects. The usual random access range methods (like @ref at, @ref size or + * @ref front) are provided. Once populated, `flat_tree` can't be modified, + * except for @ref clear and assignment. + * + * `flat_tree` models `std::ranges::contiguous_range`. * * A `flat_tree` is conceptually similar to a pair of `std::vector` objects, one holding * @ref resp3::node_view objects, and another owning the the string data that these views @@ -259,10 +263,6 @@ class flat_tree { */ std::size_t size() const noexcept { return node_tmp_offset_; } - friend bool operator==(flat_tree const&, flat_tree const&); - - friend bool operator!=(flat_tree const&, flat_tree const&); - /** @brief Reserves capacity for incoming data. * * Adding nodes (e.g. by passing the tree to `async_exec`) @@ -284,7 +284,7 @@ class flat_tree { /** @brief Clears the tree so it contains no nodes. * * Calling this function removes every node, making - * @ref get_view return empty and @ref get_total_msgs + * the range contain no nodes, and @ref get_total_msgs * return zero. It does not modify the object's capacity. * * To re-use a `flat_tree` for several requests, From ef90b2b66ee06c9696449c200431481d087a211e Mon Sep 17 00:00:00 2001 From: Ruben Perez Date: Sun, 18 Jan 2026 16:14:36 +0100 Subject: [PATCH 18/18] fix contiguous_range statement --- include/boost/redis/resp3/flat_tree.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/redis/resp3/flat_tree.hpp b/include/boost/redis/resp3/flat_tree.hpp index d62a5fc3..2783e9ad 100644 --- a/include/boost/redis/resp3/flat_tree.hpp +++ b/include/boost/redis/resp3/flat_tree.hpp @@ -51,7 +51,7 @@ struct flat_buffer { * @ref front) are provided. Once populated, `flat_tree` can't be modified, * except for @ref clear and assignment. * - * `flat_tree` models `std::ranges::contiguous_range`. + * `flat_tree` models `std::ranges::contiguous_range`. * * A `flat_tree` is conceptually similar to a pair of `std::vector` objects, one holding * @ref resp3::node_view objects, and another owning the the string data that these views