From 554a97e3c7f57202a98b6b7c23e928f690efcb91 Mon Sep 17 00:00:00 2001 From: Hristo Hristov Date: Fri, 4 Jul 2025 18:22:03 +0300 Subject: [PATCH 1/2] WIP [libc++][string] P3044R2: sub-`string_view` from `string` Implements [P3044R2](https://wg21.link/P3044R2) - https://github.com/cplusplus/draft/pull/7975 --- libcxx/include/string | 9 + libcxx/include/string_view | 8 + .../string.ops/string_substr/subview.pass.cpp | 33 ++++ .../string.view.ops/substr.pass.cpp | 125 -------------- .../string.view.ops/substr_subview.pass.cpp | 158 ++++++++++++++++++ 5 files changed, 208 insertions(+), 125 deletions(-) create mode 100644 libcxx/test/std/strings/basic.string/string.ops/string_substr/subview.pass.cpp delete mode 100644 libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp create mode 100644 libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp diff --git a/libcxx/include/string b/libcxx/include/string index c450ddcb41914..9476c3df3b8e7 100644 --- a/libcxx/include/string +++ b/libcxx/include/string @@ -280,6 +280,8 @@ public: basic_string substr(size_type pos = 0, size_type n = npos) const; // constexpr in C++20, removed in C++23 basic_string substr(size_type pos = 0, size_type n = npos) const&; // since C++23 constexpr basic_string substr(size_type pos = 0, size_type n = npos) &&; // since C++23 + constexpr basic_string_view subview(size_type pos = 0, + size_type n = npos) const; // since C++26 void swap(basic_string& str) noexcept(allocator_traits::propagate_on_container_swap::value || allocator_traits::is_always_equal::value); // C++17, constexpr since C++20 @@ -1745,6 +1747,13 @@ public: } # endif +# if _LIBCPP_STD_VER >= 26 + _LIBCPP_HIDE_FROM_ABI constexpr basic_string_view<_CharT, _Traits> + subview(size_type __pos = 0, size_type __n = npos) const { + return basic_string_view<_CharT, _Traits>(*this).subview(__pos, __n); + } +# endif + _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void swap(basic_string& __str) # if _LIBCPP_STD_VER >= 14 _NOEXCEPT; diff --git a/libcxx/include/string_view b/libcxx/include/string_view index 861187c0640e1..ad036882e1630 100644 --- a/libcxx/include/string_view +++ b/libcxx/include/string_view @@ -130,6 +130,8 @@ namespace std { size_type copy(charT* s, size_type n, size_type pos = 0) const; // constexpr in C++20 constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const; + constexpr basic_string_view subview(size_type pos = 0, + size_type n = npos) const; // freestanding-deleted, since C++26 constexpr int compare(basic_string_view s) const noexcept; constexpr int compare(size_type pos1, size_type n1, basic_string_view s) const; constexpr int compare(size_type pos1, size_type n1, @@ -465,6 +467,12 @@ public: : basic_string_view(__assume_valid(), data() + __pos, std::min(__n, size() - __pos)); } +# if _LIBCPP_STD_VER >= 26 + _LIBCPP_HIDE_FROM_ABI constexpr basic_string_view subview(size_type __pos = 0, size_type __n = npos) const { + return this->substr(__pos, __n); + } +# endif + _LIBCPP_CONSTEXPR_SINCE_CXX14 int compare(basic_string_view __sv) const _NOEXCEPT { size_type __rlen = std::min(size(), __sv.size()); int __retval = _Traits::compare(data(), __sv.data(), __rlen); diff --git a/libcxx/test/std/strings/basic.string/string.ops/string_substr/subview.pass.cpp b/libcxx/test/std/strings/basic.string/string.ops/string_substr/subview.pass.cpp new file mode 100644 index 0000000000000..65418dcb4d7be --- /dev/null +++ b/libcxx/test/std/strings/basic.string/string.ops/string_substr/subview.pass.cpp @@ -0,0 +1,33 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 + +// + +// constexpr basic_string_view<_CharT, _Traits> subview(size_type __pos = 0, size_type __n = npos) const; + +#include +#include + +constexpr bool test() { + std::string s{"Hello cruel world!"}; + auto sub = s.subview(6); + assert(sub == "cruel world!"); + auto subsub = sub.subview(0, 5); + assert(subsub == "cruel"); + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp b/libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp deleted file mode 100644 index 62b0259c175f8..0000000000000 --- a/libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp +++ /dev/null @@ -1,125 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -// UNSUPPORTED: !stdlib=libc++ && (c++03 || c++11 || c++14) - -// - -// constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const; - -// Throws: out_of_range if pos > size(). -// Effects: Determines the effective length rlen of the string to reference as the smaller of n and size() - pos. -// Returns: basic_string_view(data()+pos, rlen). - -#include -#include -#include -#include -#include - -#include "test_macros.h" - -template -void test1(std::basic_string_view sv, std::size_t n, size_t pos) { - std::basic_string_view sv1; -#ifdef TEST_HAS_NO_EXCEPTIONS - if (pos > sv.size()) - return; // would throw if exceptions were enabled - sv1 = sv.substr(pos, n); -#else - try { - sv1 = sv.substr(pos, n); - assert(pos <= sv.size()); - } catch (const std::out_of_range&) { - assert(pos > sv.size()); - return; - } -#endif - const std::size_t rlen = std::min(n, sv.size() - pos); - assert(sv1.size() == rlen); - for (std::size_t i = 0; i < rlen; ++i) - assert(sv[pos + i] == sv1[i]); -} - -template -void test(const CharT* s) { - typedef std::basic_string_view string_view_t; - - string_view_t sv1(s); - - test1(sv1, 0, 0); - test1(sv1, 1, 0); - test1(sv1, 20, 0); - test1(sv1, sv1.size(), 0); - - test1(sv1, 0, 3); - test1(sv1, 2, 3); - test1(sv1, 100, 3); - - test1(sv1, 0, string_view_t::npos); - test1(sv1, 2, string_view_t::npos); - test1(sv1, sv1.size(), string_view_t::npos); - - test1(sv1, sv1.size() + 1, 0); - test1(sv1, sv1.size() + 1, 1); - test1(sv1, sv1.size() + 1, string_view_t::npos); -} - -int main(int, char**) { - test("ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); - test("ABCDE"); - test("a"); - test(""); - -#ifndef TEST_HAS_NO_WIDE_CHARACTERS - test(L"ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); - test(L"ABCDE"); - test(L"a"); - test(L""); -#endif - -#if TEST_STD_VER >= 11 - test(u"ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); - test(u"ABCDE"); - test(u"a"); - test(u""); - - test(U"ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); - test(U"ABCDE"); - test(U"a"); - test(U""); -#endif - -#if TEST_STD_VER > 11 - { - constexpr std::string_view sv1{"ABCDE", 5}; - - { - constexpr std::string_view sv2 = sv1.substr(0, 3); - static_assert(sv2.size() == 3, ""); - static_assert(sv2[0] == 'A', ""); - static_assert(sv2[1] == 'B', ""); - static_assert(sv2[2] == 'C', ""); - } - - { - constexpr std::string_view sv2 = sv1.substr(3, 0); - static_assert(sv2.size() == 0, ""); - } - - { - constexpr std::string_view sv2 = sv1.substr(3, 3); - static_assert(sv2.size() == 2, ""); - static_assert(sv2[0] == 'D', ""); - static_assert(sv2[1] == 'E', ""); - } - } -#endif - - return 0; -} diff --git a/libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp b/libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp new file mode 100644 index 0000000000000..c40c771db4bae --- /dev/null +++ b/libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp @@ -0,0 +1,158 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: !stdlib=libc++ && (c++03 || c++11 || c++14) + +// + +// constexpr basic_string_view substr(size_type pos = 0, size_type n = npos) const; +// constexpr basic_string_view subview(size_type pos = 0, +// size_type n = npos) const; // freestanding-deleted + +// subview is alternative name of substr + +// Throws: out_of_range if pos > size(). +// Effects: Determines the effective length rlen of the string to reference as the smaller of n and size() - pos. +// Returns: basic_string_view(data()+pos, rlen). + +#include +#include +#include +#include +#include + +#include "test_macros.h" + +template +struct Test { + typedef std::basic_string_view (std::basic_string_view::*Sub)( + typename std::basic_string_view::size_type, typename std::basic_string_view::size_type) const; +}; + +template +void testDetail(std::basic_string_view sv, typename Test::Sub testSub, std::size_t n, size_t pos) { + (void)testSub; // Avoid unused parameter warning + std::basic_string_view sv1; +#ifdef TEST_HAS_NO_EXCEPTIONS + if (pos > sv.size()) + return; // would throw if exceptions were enabled + sv1 = (sv.*testSub)(pos, n); +#else + try { + sv1 = (sv.*testSub)(pos, n); + assert(pos <= sv.size()); + } catch (const std::out_of_range&) { + assert(pos > sv.size()); + return; + } +#endif + const std::size_t rlen = std::min(n, sv.size() - pos); + assert(sv1.size() == rlen); + for (std::size_t i = 0; i < rlen; ++i) + assert(sv[pos + i] == sv1[i]); +} + +template +void testCases(std::basic_string_view sv, typename Test::Sub testSub) { + testDetail(sv, testSub, 0, 0); + testDetail(sv, testSub, 1, 0); + testDetail(sv, testSub, 20, 0); + testDetail(sv, testSub, sv.size(), 0); + + testDetail(sv, testSub, 100, 3); + + testDetail(sv, testSub, 0, std::basic_string_view::npos); + testDetail(sv, testSub, 2, std::basic_string_view::npos); + testDetail(sv, testSub, sv.size(), std::basic_string_view::npos); + + testDetail(sv, testSub, sv.size() + 1, 0); + testDetail(sv, testSub, sv.size() + 1, 1); + testDetail(sv, testSub, sv.size() + 1, std::basic_string_view::npos); +} + +template +void testSubs(const CharT* s) { + std::basic_string_view sv(s); + + testCases(sv, &std::basic_string_view::substr); +#if TEST_STD_VER >= 26 + testCases(sv, &std::basic_string_view::subview); +#endif // TEST_STD_VER >= 26 +} + +void test() { + testSubs("ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); + testSubs("ABCDE"); + testSubs("a"); + testSubs(""); + +#ifndef TEST_HAS_NO_WIDE_CHARACTERS + testSubs( + L"ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); + testSubs(L"ABCDE"); + testSubs(L"a"); + testSubs(L""); +#endif + +#if TEST_STD_VER >= 11 + testSubs( + u"ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); + testSubs(u"ABCDE"); + testSubs(u"a"); + testSubs(u""); + + testSubs( + U"ABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDEABCDE"); + testSubs(U"ABCDE"); + testSubs(U"a"); + testSubs(U""); +#endif +} + +#if TEST_STD_VER >= 14 +template ::Sub TestSub> +constexpr void testConstexprDetail() { + constexpr std::string_view sv{"ABCDE", 5}; + { + constexpr std::string_view sv2 = (sv.*TestSub)(0, 3); + + static_assert(sv2.size() == 3, ""); + static_assert(sv2[0] == 'A', ""); + static_assert(sv2[1] == 'B', ""); + static_assert(sv2[2] == 'C', ""); + } + + { + constexpr std::string_view sv2 = (sv.*TestSub)(3, 0); + static_assert(sv2.size() == 0, ""); + } + + { + constexpr std::string_view sv2 = (sv.*TestSub)(3, 3); + static_assert(sv2.size() == 2, ""); + static_assert(sv2[0] == 'D', ""); + static_assert(sv2[1] == 'E', ""); + } +} + +void test_constexpr() { + testConstexprDetail<&std::string_view::substr>(); +# if TEST_STD_VER >= 26 + testConstexprDetail<&std::string_view::subview>(); +# endif +} +#endif + +int main(int, char**) { + test(); +#if TEST_STD_VER >= 14 + test_constexpr(); +#endif + + return 0; +} From e477951972d74bee906cb78e9380c2577efd89e7 Mon Sep 17 00:00:00 2001 From: Hristo Hristov Date: Fri, 4 Jul 2025 21:26:04 +0300 Subject: [PATCH 2/2] Renamed test --- ...ubstr_subview.pass.cpp => substr.pass.cpp} | 43 +++++++++---------- .../string.view.ops/subview.pass.cpp | 20 +++++++++ 2 files changed, 41 insertions(+), 22 deletions(-) rename libcxx/test/std/strings/string.view/string.view.ops/{substr_subview.pass.cpp => substr.pass.cpp} (77%) create mode 100644 libcxx/test/std/strings/string.view/string.view.ops/subview.pass.cpp diff --git a/libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp b/libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp similarity index 77% rename from libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp rename to libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp index c40c771db4bae..02755985ead70 100644 --- a/libcxx/test/std/strings/string.view/string.view.ops/substr_subview.pass.cpp +++ b/libcxx/test/std/strings/string.view/string.view.ops/substr.pass.cpp @@ -34,17 +34,16 @@ struct Test { typename std::basic_string_view::size_type, typename std::basic_string_view::size_type) const; }; -template -void testDetail(std::basic_string_view sv, typename Test::Sub testSub, std::size_t n, size_t pos) { - (void)testSub; // Avoid unused parameter warning +template ::Sub TestSub> +void testDetail(std::basic_string_view sv, std::size_t n, size_t pos) { std::basic_string_view sv1; #ifdef TEST_HAS_NO_EXCEPTIONS if (pos > sv.size()) return; // would throw if exceptions were enabled - sv1 = (sv.*testSub)(pos, n); + sv1 = (sv.*TestSub)(pos, n); #else try { - sv1 = (sv.*testSub)(pos, n); + sv1 = (sv.*TestSub)(pos, n); assert(pos <= sv.size()); } catch (const std::out_of_range&) { assert(pos > sv.size()); @@ -57,31 +56,31 @@ void testDetail(std::basic_string_view sv, typename Test::Sub test assert(sv[pos + i] == sv1[i]); } -template -void testCases(std::basic_string_view sv, typename Test::Sub testSub) { - testDetail(sv, testSub, 0, 0); - testDetail(sv, testSub, 1, 0); - testDetail(sv, testSub, 20, 0); - testDetail(sv, testSub, sv.size(), 0); +template ::Sub TestSub> +void testCases(const CharT* s) { + std::basic_string_view sv(s); - testDetail(sv, testSub, 100, 3); + testDetail(sv, 0, 0); + testDetail(sv, 1, 0); + testDetail(sv, 20, 0); + testDetail(sv, sv.size(), 0); - testDetail(sv, testSub, 0, std::basic_string_view::npos); - testDetail(sv, testSub, 2, std::basic_string_view::npos); - testDetail(sv, testSub, sv.size(), std::basic_string_view::npos); + testDetail(sv, 100, 3); - testDetail(sv, testSub, sv.size() + 1, 0); - testDetail(sv, testSub, sv.size() + 1, 1); - testDetail(sv, testSub, sv.size() + 1, std::basic_string_view::npos); + testDetail(sv, 0, std::basic_string_view::npos); + testDetail(sv, 2, std::basic_string_view::npos); + testDetail(sv, sv.size(), std::basic_string_view::npos); + + testDetail(sv, sv.size() + 1, 0); + testDetail(sv, sv.size() + 1, 1); + testDetail(sv, sv.size() + 1, std::basic_string_view::npos); } template void testSubs(const CharT* s) { - std::basic_string_view sv(s); - - testCases(sv, &std::basic_string_view::substr); + testCases::substr>(s); #if TEST_STD_VER >= 26 - testCases(sv, &std::basic_string_view::subview); + testCases::subview>(s); #endif // TEST_STD_VER >= 26 } diff --git a/libcxx/test/std/strings/string.view/string.view.ops/subview.pass.cpp b/libcxx/test/std/strings/string.view/string.view.ops/subview.pass.cpp new file mode 100644 index 0000000000000..fec258eef4546 --- /dev/null +++ b/libcxx/test/std/strings/string.view/string.view.ops/subview.pass.cpp @@ -0,0 +1,20 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 + +// + +// constexpr basic_string_view subview(size_type pos = 0, +// size_type n = npos) const; // freestanding-deleted + +int main(int, char**) { + // This test is intentionally empty because subview is an alias for substr + // and is tested in substr.pass.cpp. + return 0; +}