diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index 24398574064e6..7fbccaad2fa99 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -46,7 +46,7 @@ "`P2255R2 `__","A type trait to detect reference binding to temporary","2022-02 (Virtual)","","","" "`P2273R3 `__","Making ``std::unique_ptr`` constexpr","2022-02 (Virtual)","|Complete|","16","" "`P2387R3 `__","Pipe support for user-defined range adaptors","2022-02 (Virtual)","|Complete|","19","" -"`P2440R1 `__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","","","" +"`P2440R1 `__","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","2022-02 (Virtual)","|In Progress|","","" "`P2441R2 `__","``views::join_with``","2022-02 (Virtual)","|In Progress|","","" "`P2442R1 `__","Windowing range adaptors: ``views::chunk`` and ``views::slide``","2022-02 (Virtual)","","","" "`P2443R1 `__","``views::chunk_by``","2022-02 (Virtual)","|Complete|","18","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 0b484ebe5e87c..d71e5d32ffbe4 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -154,6 +154,7 @@ set(files __algorithm/ranges_set_intersection.h __algorithm/ranges_set_symmetric_difference.h __algorithm/ranges_set_union.h + __algorithm/ranges_shift_left.h __algorithm/ranges_shuffle.h __algorithm/ranges_sort.h __algorithm/ranges_sort_heap.h diff --git a/libcxx/include/__algorithm/ranges_shift_left.h b/libcxx/include/__algorithm/ranges_shift_left.h new file mode 100644 index 0000000000000..7d494820cc412 --- /dev/null +++ b/libcxx/include/__algorithm/ranges_shift_left.h @@ -0,0 +1,73 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef _LIBCPP___ALGORITHM_RANGES_SHIFT_LEFT_H +#define _LIBCPP___ALGORITHM_RANGES_SHIFT_LEFT_H + +#include <__algorithm/iterator_operations.h> +#include <__algorithm/shift_left.h> +#include <__config> +#include <__iterator/concepts.h> +#include <__iterator/distance.h> +#include <__iterator/incrementable_traits.h> +#include <__iterator/permutable.h> +#include <__ranges/access.h> +#include <__ranges/concepts.h> +#include <__ranges/subrange.h> +#include <__utility/move.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_PUSH_MACROS +#include <__undef_macros> + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 23 + +namespace ranges { +namespace __shift_left { + +struct __fn { + template _Sent> + _LIBCPP_HIDE_FROM_ABI static constexpr subrange<_Iter> + operator()(_Iter __first, _Sent __last, iter_difference_t<_Iter> __n) { + auto __ret = std::__shift_left<_RangeAlgPolicy>(std::move(__first), std::move(__last), std::move(__n)); + return {std::move(__ret.first), std::move(__ret.second)}; + } + + template + requires permutable> + _LIBCPP_HIDE_FROM_ABI static constexpr borrowed_subrange_t<_Range> + operator()(_Range&& __range, range_difference_t<_Range> __n) { + if constexpr (sized_range<_Range>) { + if (__n >= ranges::distance(__range)) { + return {ranges::begin(__range), ranges::begin(__range)}; + } + } + + auto __ret = std::__shift_left<_RangeAlgPolicy>(ranges::begin(__range), ranges::end(__range), std::move(__n)); + return {std::move(__ret.first), std::move(__ret.second)}; + } +}; +} // namespace __shift_left + +inline namespace __cpo { +inline constexpr auto shift_left = __shift_left::__fn{}; +} // namespace __cpo +} // namespace ranges + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___ALGORITHM_RANGES_SHIFT_LEFT_H diff --git a/libcxx/include/__algorithm/shift_left.h b/libcxx/include/__algorithm/shift_left.h index 06cd7c5f87644..deb6ac920dc1e 100644 --- a/libcxx/include/__algorithm/shift_left.h +++ b/libcxx/include/__algorithm/shift_left.h @@ -9,9 +9,14 @@ #ifndef _LIBCPP___ALGORITHM_SHIFT_LEFT_H #define _LIBCPP___ALGORITHM_SHIFT_LEFT_H +#include <__algorithm/iterator_operations.h> #include <__algorithm/move.h> +#include <__assert> #include <__config> +#include <__iterator/concepts.h> #include <__iterator/iterator_traits.h> +#include <__utility/move.h> +#include <__utility/pair.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -24,30 +29,42 @@ _LIBCPP_BEGIN_NAMESPACE_STD #if _LIBCPP_STD_VER >= 20 -template -inline _LIBCPP_HIDE_FROM_ABI constexpr _ForwardIterator -shift_left(_ForwardIterator __first, - _ForwardIterator __last, - typename iterator_traits<_ForwardIterator>::difference_type __n) { +template +_LIBCPP_HIDE_FROM_ABI constexpr pair<_Iter, _Iter> +__shift_left(_Iter __first, _Sent __last, typename _IterOps<_AlgPolicy>::template __difference_type<_Iter> __n) { + _LIBCPP_ASSERT_UNCATEGORIZED(__n >= 0, "__n must be greater than or equal to 0"); + if (__n == 0) { - return __last; + _Iter __end = _IterOps<_AlgPolicy>::next(__first, __last); + return {std::move(__first), std::move(__end)}; } - _ForwardIterator __m = __first; - if constexpr (__has_random_access_iterator_category<_ForwardIterator>::value) { - if (__n >= __last - __first) { - return __first; + _Iter __m = __first; + if constexpr (sized_sentinel_for<_Sent, _Iter>) { + auto __size = _IterOps<_AlgPolicy>::distance(__first, __last); + if (__n >= __size) { + return {__first, __first}; } - __m += __n; + _IterOps<_AlgPolicy>::advance(__m, __n); } else { for (; __n > 0; --__n) { if (__m == __last) { - return __first; + return {__first, __first}; } ++__m; } } - return std::move(__m, __last, __first); + + _Iter __result = std::__move<_AlgPolicy>(__m, __last, __first).second; + return {std::move(__first), std::move(__result)}; +} + +template +inline _LIBCPP_HIDE_FROM_ABI constexpr _ForwardIterator +shift_left(_ForwardIterator __first, + _ForwardIterator __last, + typename iterator_traits<_ForwardIterator>::difference_type __n) { + return std::__shift_left<_ClassicAlgPolicy>(std::move(__first), std::move(__last), __n).second; } #endif // _LIBCPP_STD_VER >= 20 diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm index e593ae26ed6e2..702444240b26b 100644 --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -2027,6 +2027,7 @@ template # include <__algorithm/ranges_ends_with.h> # include <__algorithm/ranges_find_last.h> # include <__algorithm/ranges_fold.h> +# include <__algorithm/ranges_shift_left.h> # include <__algorithm/ranges_starts_with.h> # endif // _LIBCPP_STD_VER >= 23 diff --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap index 86efbd36b20d1..ca59c75b73e95 100644 --- a/libcxx/include/module.modulemap +++ b/libcxx/include/module.modulemap @@ -749,6 +749,9 @@ module std [system] { export std.functional.ranges_operations export std.algorithm.in_in_out_result } + module ranges_shift_left { + header "__algorithm/ranges_shift_left.h" + } module ranges_shuffle { header "__algorithm/ranges_shuffle.h" } diff --git a/libcxx/modules/std/algorithm.inc b/libcxx/modules/std/algorithm.inc index 3c2139cd64ee4..3c2c8b79bc65c 100644 --- a/libcxx/modules/std/algorithm.inc +++ b/libcxx/modules/std/algorithm.inc @@ -354,9 +354,11 @@ export namespace std { // [alg.shift], shift using std::shift_left; +#if _LIBCPP_STD_VER >= 23 namespace ranges { - // using std::ranges::shift_left; + using std::ranges::shift_left; } +#endif // _LIBCPP_STD_VER >= 23 using std::shift_right; diff --git a/libcxx/test/benchmarks/algorithms/ranges_shift_left.bench.cpp b/libcxx/test/benchmarks/algorithms/ranges_shift_left.bench.cpp new file mode 100644 index 0000000000000..9ba253c3dc15d --- /dev/null +++ b/libcxx/test/benchmarks/algorithms/ranges_shift_left.bench.cpp @@ -0,0 +1,69 @@ +//===----------------------------------------------------------------------===// +// +// 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: c++03, c++11, c++14, c++17, c++20 + +#include +#include +#include +#include + +#include "test_iterators.h" + +static void bm_shift_left_random_access_range_with_sized_sentinel(benchmark::State& state) { + std::vector a(state.range(), 1); + + for (auto _ : state) { + benchmark::DoNotOptimize(a); + + auto begin = random_access_iterator(a.data()); + auto end = random_access_iterator(a.data() + a.size()); + + static_assert(std::random_access_iterator); + static_assert(std::sized_sentinel_for); + + benchmark::DoNotOptimize(std::ranges::shift_left(begin, end, a.size() / 2)); + } +} +BENCHMARK(bm_shift_left_random_access_range_with_sized_sentinel)->RangeMultiplier(16)->Range(16, 16 << 20); + +static void bm_shift_left_forward_range_with_sized_sentinel(benchmark::State& state) { + std::vector a(state.range(), 1); + + for (auto _ : state) { + benchmark::DoNotOptimize(a); + + auto begin = forward_iterator(a.data()); + auto end = sized_sentinel(forward_iterator(a.data() + a.size())); + + static_assert(!std::random_access_iterator); + static_assert(std::sized_sentinel_for); + + benchmark::DoNotOptimize(std::ranges::shift_left(begin, end, a.size() / 2)); + } +} +BENCHMARK(bm_shift_left_forward_range_with_sized_sentinel)->RangeMultiplier(16)->Range(16, 16 << 20); + +static void bm_shift_left_forward_range(benchmark::State& state) { + std::vector a(state.range(), 1); + + for (auto _ : state) { + benchmark::DoNotOptimize(a); + + auto begin = forward_iterator(a.data()); + auto end = forward_iterator(a.data() + a.size()); + + static_assert(!std::random_access_iterator); + static_assert(!std::sized_sentinel_for); + + benchmark::DoNotOptimize(std::ranges::shift_left(begin, end, a.size() / 2)); + } +} +BENCHMARK(bm_shift_left_forward_range)->RangeMultiplier(16)->Range(16, 16 << 20); + +BENCHMARK_MAIN(); diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.shift/ranges.shift_left.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.shift/ranges.shift_left.pass.cpp new file mode 100644 index 0000000000000..1bd8f37551b55 --- /dev/null +++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.shift/ranges.shift_left.pass.cpp @@ -0,0 +1,195 @@ +//===----------------------------------------------------------------------===// +// +// 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: c++03, c++11, c++14, c++17, c++20 + +// template S> +// constexpr subrange ranges::shift_left(I first, S last, iter_difference_t n); + +// template +// requires permutable> +// constexpr borrowed_subrange_t ranges::shift_left(R&& r, range_difference_t n) + +#include +#include +#include +#include +#include + +#include "almost_satisfies_types.h" +#include "test_iterators.h" +#include "MoveOnly.h" + +struct InvalidDifferenceT {}; + +template > +concept HasShiftLeftIt = requires(Iter iter, Sent sent, N n) { std::ranges::shift_left(iter, sent, n); }; + +static_assert(HasShiftLeftIt); +static_assert(HasShiftLeftIt>); +static_assert(HasShiftLeftIt>); +static_assert(!HasShiftLeftIt); + +static_assert(!HasShiftLeftIt); +static_assert(!HasShiftLeftIt); +static_assert(!HasShiftLeftIt); + +template > +concept HasShiftLeftR = requires(Range range, N n) { std::ranges::shift_left(range, n); }; + +static_assert(HasShiftLeftR>); +static_assert(!HasShiftLeftR, InvalidDifferenceT>); + +static_assert(!HasShiftLeftR); +static_assert(!HasShiftLeftR); +static_assert(!HasShiftLeftR); + +template +constexpr void test_iter_sent() { + { + const std::array original = {3, 1, 4, 1, 5, 9, 2, 6}; + std::array scratch; + + // (iterator, sentinel) overload + for (size_t n = 0; n <= original.size(); ++n) { + for (size_t k = 0; k <= n + 2; ++k) { + auto begin = Iter(scratch.data()); + auto end = Sent(Iter(scratch.data() + n)); + std::ranges::copy(original.begin(), original.begin() + n, begin); + auto result = std::ranges::shift_left(begin, end, k); + + assert(result.begin() == begin); + if (k < n) { + assert(result.end() == Iter(scratch.data() + n - k)); + assert(std::ranges::equal(original.begin() + k, original.begin() + n, result.begin(), result.end())); + } else { + assert(result.end() == begin); + assert(std::ranges::equal(original.begin(), original.begin() + n, begin, end)); + } + } + } + + // (range) overload + for (size_t n = 0; n <= original.size(); ++n) { + for (size_t k = 0; k <= n + 2; ++k) { + auto begin = Iter(scratch.data()); + auto end = Sent(Iter(scratch.data() + n)); + std::ranges::copy(original.begin(), original.begin() + n, begin); + auto range = std::ranges::subrange(begin, end); + auto result = std::ranges::shift_left(range, k); + + assert(result.begin() == begin); + if (k < n) { + assert(result.end() == Iter(scratch.data() + n - k)); + assert(std::ranges::equal(original.begin() + k, original.begin() + n, begin, result.end())); + } else { + assert(result.end() == begin); + assert(std::ranges::equal(original.begin(), original.begin() + n, begin, end)); + } + } + } + } + + // n == 0 + { + std::array input = {0, 1, 2}; + const std::array expected = {0, 1, 2}; + + { // (iterator, sentinel) overload + auto in = input; + auto begin = Iter(in.data()); + auto end = Sent(Iter(in.data() + in.size())); + auto result = std::ranges::shift_left(begin, end, 0); + assert(std::ranges::equal(expected, result)); + assert(result.begin() == begin); + assert(result.end() == end); + } + + { // (range) overload + auto in = input; + auto begin = Iter(in.data()); + auto end = Sent(Iter(in.data() + in.size())); + auto range = std::ranges::subrange(begin, end); + auto result = std::ranges::shift_left(range, 0); + assert(std::ranges::equal(expected, result)); + assert(result.begin() == begin); + assert(result.end() == end); + } + } + + // n == len + { + std::array input = {0, 1, 2}; + const std::array expected = {0, 1, 2}; + + { // (iterator, sentinel) overload + auto in = input; + auto begin = Iter(in.data()); + auto end = Sent(Iter(in.data() + in.size())); + auto result = std::ranges::shift_left(begin, end, input.size()); + assert(std::ranges::equal(expected, input)); + assert(result.begin() == begin); + assert(result.end() == begin); + } + + { // (range) overload + auto in = input; + auto begin = Iter(in.data()); + auto end = Sent(Iter(in.data() + in.size())); + auto range = std::ranges::subrange(begin, end); + auto result = std::ranges::shift_left(range, input.size()); + assert(std::ranges::equal(expected, input)); + assert(result.begin() == begin); + assert(result.end() == begin); + } + } + + // n > len + { + std::array input = {0, 1, 2}; + const std::array expected = {0, 1, 2}; + + { // (iterator, sentinel) overload + auto in = input; + auto begin = Iter(in.data()); + auto end = Sent(Iter(in.data() + in.size())); + auto result = std::ranges::shift_left(begin, end, input.size() + 1); + assert(std::ranges::equal(expected, input)); + assert(result.begin() == begin); + assert(result.end() == begin); + } + + { // (range) overload + auto in = input; + auto begin = Iter(in.data()); + auto end = Sent(Iter(in.data() + in.size())); + auto range = std::ranges::subrange(begin, end); + auto result = std::ranges::shift_left(range, input.size() + 1); + assert(std::ranges::equal(expected, input)); + assert(result.begin() == begin); + assert(result.end() == begin); + } + } +} + +constexpr bool test() { + types::for_each(types::forward_iterator_list{}, [] { + test_iter_sent(); + test_iter_sent>(); + test_iter_sent>(); + }); + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + return 0; +} diff --git a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp index ca1433b778751..1244de04a51d9 100644 --- a/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp +++ b/libcxx/test/std/algorithms/ranges_robust_against_proxy_iterators.pass.cpp @@ -182,7 +182,9 @@ constexpr void run_tests() { test(std::ranges::sort_heap, in); test(std::ranges::prev_permutation, in); test(std::ranges::next_permutation, in); - +#if TEST_STD_VER >= 23 + test(std::ranges::shift_left, in, count); +#endif // The algorithms that work on uninitialized memory have constraints that prevent proxy iterators from being used with // them. } diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp index 5a2ee189641c7..53b8901ebebe6 100644 --- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp +++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp @@ -151,6 +151,9 @@ static_assert(test(std::ranges::set_difference, a, a, a)); static_assert(test(std::ranges::set_intersection, a, a, a)); static_assert(test(std::ranges::set_symmetric_difference, a, a, a)); static_assert(test(std::ranges::set_union, a, a, a)); +#if TEST_STD_VER >= 23 +static_assert(test(std::ranges::shift_left, a, 42)); +#endif static_assert(test(std::ranges::shuffle, a, g)); static_assert(test(std::ranges::sort, a)); static_assert(test(std::ranges::sort_heap, a));