From e7325b2224c312215d287e03eb54e1d2162e3edd Mon Sep 17 00:00:00 2001 From: Alan de Freitas Date: Fri, 23 Jan 2026 17:36:37 -0500 Subject: [PATCH] feat(ranges): specialize enable_borrowed_range for view types All view types in Boost.URL have iterators that store raw pointers to external data rather than references to the view object itself. This means iterators remain valid even after the view is destroyed, satisfying the borrowed_range requirements. Specializations added for: - decode_view, pct_string_view - params_view, params_ref, params_encoded_view, params_encoded_ref - segments_view, segments_ref, segments_encoded_view, segments_encoded_ref fix #927 --- include/boost/url/decode_view.hpp | 16 ++++++++++++++ include/boost/url/params_encoded_ref.hpp | 16 ++++++++++++++ include/boost/url/params_encoded_view.hpp | 16 ++++++++++++++ include/boost/url/params_ref.hpp | 16 ++++++++++++++ include/boost/url/params_view.hpp | 16 ++++++++++++++ include/boost/url/pct_string_view.hpp | 10 +++++++++ include/boost/url/segments_encoded_ref.hpp | 16 ++++++++++++++ include/boost/url/segments_encoded_view.hpp | 16 ++++++++++++++ include/boost/url/segments_ref.hpp | 16 ++++++++++++++ include/boost/url/segments_view.hpp | 16 ++++++++++++++ test/unit/decode_view.cpp | 20 ++++++++++++++++++ test/unit/params_encoded_ref.cpp | 22 ++++++++++++++++++++ test/unit/params_encoded_view.cpp | 20 ++++++++++++++++++ test/unit/params_ref.cpp | 23 +++++++++++++++++++++ test/unit/params_view.cpp | 20 ++++++++++++++++++ test/unit/pct_string_view.cpp | 21 +++++++++++++++++++ test/unit/segments_encoded_ref.cpp | 22 ++++++++++++++++++++ test/unit/segments_encoded_view.cpp | 20 ++++++++++++++++++ test/unit/segments_ref.cpp | 23 +++++++++++++++++++++ test/unit/segments_view.cpp | 20 ++++++++++++++++++ 20 files changed, 365 insertions(+) diff --git a/include/boost/url/decode_view.hpp b/include/boost/url/decode_view.hpp index 172591848..9a58a73a9 100644 --- a/include/boost/url/decode_view.hpp +++ b/include/boost/url/decode_view.hpp @@ -1074,4 +1074,20 @@ make_decode_view( #include +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::decode_view> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/params_encoded_ref.hpp b/include/boost/url/params_encoded_ref.hpp index 4ac8d230a..f6cd7fe62 100644 --- a/include/boost/url/params_encoded_ref.hpp +++ b/include/boost/url/params_encoded_ref.hpp @@ -1005,4 +1005,20 @@ class BOOST_URL_DECL params_encoded_ref // // #include +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::params_encoded_ref> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/params_encoded_view.hpp b/include/boost/url/params_encoded_view.hpp index ec553e862..7febd7b7f 100644 --- a/include/boost/url/params_encoded_view.hpp +++ b/include/boost/url/params_encoded_view.hpp @@ -239,4 +239,20 @@ class BOOST_URL_DECL params_encoded_view } // urls } // boost +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::params_encoded_view> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/params_ref.hpp b/include/boost/url/params_ref.hpp index 5ce4d5bc1..7ce409764 100644 --- a/include/boost/url/params_ref.hpp +++ b/include/boost/url/params_ref.hpp @@ -961,4 +961,20 @@ class BOOST_URL_DECL params_ref // // #include +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::params_ref> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/params_view.hpp b/include/boost/url/params_view.hpp index 4ff9f63b8..559309021 100644 --- a/include/boost/url/params_view.hpp +++ b/include/boost/url/params_view.hpp @@ -287,4 +287,20 @@ class params_view } // urls } // boost +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::params_view> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/pct_string_view.hpp b/include/boost/url/pct_string_view.hpp index d7861d30b..b77aab70e 100644 --- a/include/boost/url/pct_string_view.hpp +++ b/include/boost/url/pct_string_view.hpp @@ -467,4 +467,14 @@ to_sv(pct_string_view const& s) noexcept // Ensure decode_view is complete for operator*() #include +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::pct_string_view> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/segments_encoded_ref.hpp b/include/boost/url/segments_encoded_ref.hpp index 30abc4633..802fef9a6 100644 --- a/include/boost/url/segments_encoded_ref.hpp +++ b/include/boost/url/segments_encoded_ref.hpp @@ -794,4 +794,20 @@ class segments_encoded_ref // // #include +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::segments_encoded_ref> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/segments_encoded_view.hpp b/include/boost/url/segments_encoded_view.hpp index db1ebed0a..aad2efd18 100644 --- a/include/boost/url/segments_encoded_view.hpp +++ b/include/boost/url/segments_encoded_view.hpp @@ -307,4 +307,20 @@ class segments_encoded_view } // urls } // boost +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::segments_encoded_view> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/segments_ref.hpp b/include/boost/url/segments_ref.hpp index d9dbb3312..a99594944 100644 --- a/include/boost/url/segments_ref.hpp +++ b/include/boost/url/segments_ref.hpp @@ -740,4 +740,20 @@ class segments_ref // // #include +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::segments_ref> = true; +} // std::ranges +#endif + #endif diff --git a/include/boost/url/segments_view.hpp b/include/boost/url/segments_view.hpp index 77fb2a80d..8b921cb80 100644 --- a/include/boost/url/segments_view.hpp +++ b/include/boost/url/segments_view.hpp @@ -259,4 +259,20 @@ class segments_view } // urls } // boost +//------------------------------------------------ +// +// std::ranges::enable_borrowed_range +// +//------------------------------------------------ + +#ifdef BOOST_URL_HAS_CONCEPTS +#include +namespace std::ranges { + template<> + inline constexpr bool + enable_borrowed_range< + boost::urls::segments_view> = true; +} // std::ranges +#endif + #endif diff --git a/test/unit/decode_view.cpp b/test/unit/decode_view.cpp index 92750b73c..da84c140f 100644 --- a/test/unit/decode_view.cpp +++ b/test/unit/decode_view.cpp @@ -353,6 +353,25 @@ struct decode_view_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // decode_view is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the view is destroyed + decode_view::iterator it; + { + decode_view dv("hello%20world"); + it = dv.begin(); + } + // iterator is still valid (points to external buffer) + BOOST_TEST_EQ(*it, 'h'); +#endif + } + void run() { @@ -365,6 +384,7 @@ struct decode_view_test testCompare(); testStream(); testPR127Cases(); + testBorrowedRange(); } }; diff --git a/test/unit/params_encoded_ref.cpp b/test/unit/params_encoded_ref.cpp index c9e680bf5..10811b234 100644 --- a/test/unit/params_encoded_ref.cpp +++ b/test/unit/params_encoded_ref.cpp @@ -770,6 +770,27 @@ struct params_encoded_ref_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // params_encoded_ref is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the ref is destroyed + // (as long as the underlying url stays alive) + url u("?first=John&last=Doe"); + params_encoded_ref::iterator it; + { + params_encoded_ref p = u.encoded_params(); + it = p.begin(); + } + // ref is destroyed, but iterator is still valid + BOOST_TEST_EQ((*it).key, "first"); +#endif + } + void run() { @@ -777,6 +798,7 @@ struct params_encoded_ref_test testObservers(); testModifiers(); testJavadocs(); + testBorrowedRange(); } }; diff --git a/test/unit/params_encoded_view.cpp b/test/unit/params_encoded_view.cpp index a81b28f68..fbb7e9841 100644 --- a/test/unit/params_encoded_view.cpp +++ b/test/unit/params_encoded_view.cpp @@ -214,12 +214,32 @@ struct params_encoded_view_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // params_encoded_view is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the view is destroyed + params_encoded_view::iterator it; + { + params_encoded_view qp("first=John&last=Doe"); + it = qp.begin(); + } + // iterator is still valid (points to external buffer) + BOOST_TEST_EQ((*it).key, "first"); +#endif + } + void run() { testMembers(); testRange(); testJavadocs(); + testBorrowedRange(); } }; diff --git a/test/unit/params_ref.cpp b/test/unit/params_ref.cpp index 2e94bae81..cf68456ba 100644 --- a/test/unit/params_ref.cpp +++ b/test/unit/params_ref.cpp @@ -1032,6 +1032,28 @@ struct params_ref_test } } + static + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // params_ref is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the ref is destroyed + // (as long as the underlying url stays alive) + url u("?first=John&last=Doe"); + params_ref::iterator it; + { + params_ref p = u.params(); + it = p.begin(); + } + // ref is destroyed, but iterator is still valid + BOOST_TEST_EQ((*it).key, "first"); +#endif + } + static void testAll() @@ -1041,6 +1063,7 @@ struct params_ref_test testModifiers(); testJavadocs(); testSpaceAsPlus(); + testBorrowedRange(); } void diff --git a/test/unit/params_view.cpp b/test/unit/params_view.cpp index 5ce80e74a..7f0bed217 100644 --- a/test/unit/params_view.cpp +++ b/test/unit/params_view.cpp @@ -142,11 +142,31 @@ struct params_view_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // params_view is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the view is destroyed + params_view::iterator it; + { + params_view qp("first=John&last=Doe"); + it = qp.begin(); + } + // iterator is still valid (points to external buffer) + BOOST_TEST_EQ((*it).key, "first"); +#endif + } + void run() { testMembers(); testJavadocs(); + testBorrowedRange(); } }; diff --git a/test/unit/pct_string_view.cpp b/test/unit/pct_string_view.cpp index 04961ec69..8172c1af9 100644 --- a/test/unit/pct_string_view.cpp +++ b/test/unit/pct_string_view.cpp @@ -10,6 +10,7 @@ // Test that header file is self-contained. #include +#include #include "test_suite.hpp" namespace boost { @@ -158,12 +159,32 @@ struct pct_string_view_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // pct_string_view is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the view is destroyed + pct_string_view::iterator it; + { + pct_string_view psv("hello%20world"); + it = psv.begin(); + } + // iterator is still valid (points to external buffer) + BOOST_TEST_EQ(*it, 'h'); +#endif + } + void run() { testSpecial(); testRelation(); testOperatorStar(); + testBorrowedRange(); } }; diff --git a/test/unit/segments_encoded_ref.cpp b/test/unit/segments_encoded_ref.cpp index 80c054f4b..c08e4f131 100644 --- a/test/unit/segments_encoded_ref.cpp +++ b/test/unit/segments_encoded_ref.cpp @@ -827,6 +827,27 @@ struct segments_encoded_ref_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // segments_encoded_ref is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the ref is destroyed + // (as long as the underlying url stays alive) + url u("/path/to/file.txt"); + segments_encoded_ref::iterator it; + { + segments_encoded_ref s = u.encoded_segments(); + it = s.begin(); + } + // ref is destroyed, but iterator is still valid + BOOST_TEST_EQ(*it, "path"); +#endif + } + void run() { @@ -835,6 +856,7 @@ struct segments_encoded_ref_test testModifiers(); testEditSegments(); testJavadocs(); + testBorrowedRange(); } }; diff --git a/test/unit/segments_encoded_view.cpp b/test/unit/segments_encoded_view.cpp index dedfd7c09..136781a7d 100644 --- a/test/unit/segments_encoded_view.cpp +++ b/test/unit/segments_encoded_view.cpp @@ -191,11 +191,31 @@ struct segments_const_encoded_view_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // segments_encoded_view is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the view is destroyed + segments_encoded_view::iterator it; + { + segments_encoded_view ps("/path/to/file.txt"); + it = ps.begin(); + } + // iterator is still valid (points to external buffer) + BOOST_TEST_EQ(*it, "path"); +#endif + } + void run() { testSpecialMembers(); testJavadocs(); + testBorrowedRange(); } }; diff --git a/test/unit/segments_ref.cpp b/test/unit/segments_ref.cpp index 4e14588eb..b86d43dc9 100644 --- a/test/unit/segments_ref.cpp +++ b/test/unit/segments_ref.cpp @@ -843,6 +843,28 @@ struct segments_ref_test //-------------------------------------------- + static + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // segments_ref is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the ref is destroyed + // (as long as the underlying url stays alive) + url u("/path/to/file.txt"); + segments_ref::iterator it; + { + segments_ref s = u.segments(); + it = s.begin(); + } + // ref is destroyed, but iterator is still valid + BOOST_TEST_EQ(*it, "path"); +#endif + } + static void testAll() @@ -851,6 +873,7 @@ struct segments_ref_test testObservers(); testModifiers(); testJavadocs(); + testBorrowedRange(); } void diff --git a/test/unit/segments_view.cpp b/test/unit/segments_view.cpp index 5325306b6..f206d3493 100644 --- a/test/unit/segments_view.cpp +++ b/test/unit/segments_view.cpp @@ -386,12 +386,32 @@ struct segments_view_test } } + void + testBorrowedRange() + { +#ifdef BOOST_URL_HAS_CONCEPTS + // segments_view is a borrowed range + BOOST_CORE_STATIC_ASSERT( + std::ranges::borrowed_range); + + // iterators remain valid after the view is destroyed + segments_view::iterator it; + { + segments_view ps("/path/to/file.txt"); + it = ps.begin(); + } + // iterator is still valid (points to external buffer) + BOOST_TEST_EQ(*it, "path"); +#endif + } + void run() { testSpecialMembers(); testRangeCtor(); testJavadocs(); + testBorrowedRange(); } };