https://github.com/jamesETsmith created https://github.com/llvm/llvm-project/pull/68494
# Overview As a disclaimer, this is my first PR to LLVM and while I've tried to ensure I've followed the LLVM and libc++ contributing guidelines, there's probably a good chance I missed something. If I have, just let me know and I'll try to correct it as soon as I can. This PR implements `std::ranges::iota` and `std::ranges::out_value_result` outlined in [P2440r1](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2440r1.html). As outlined in the paper above, I've: - Implemented `out_value_result` and added to `<algorithm>` - Added `out_value_result`, `iota_result`, and two overloads of `iota` to `std::ranges` in `<numeric>` - Updated the version macro `__cpp_lib_ranges_iota` in `<version>` I've also added tests for `ranges::iota` and `ranges::out_value_result`. Lastly, I added those structs to the appropriate module files. # TODOs - [ ] Updating the range [status doc](https://github.com/jamesETsmith/llvm-project/blob/main/libcxx/docs/Status/RangesMajorFeatures.csv) - [ ] I'm open to implementing the rest of P2440r1 (`ranges::shift_left` and `ranges::shift_right`) if that's ok, I just wanted to get feedback on `ranges::iota` first - [ ] I've been having trouble building the modules locally and want to make sure that's working properly >From c4a3ccfbad090ad8314aa8ad53092edc8d5432bc Mon Sep 17 00:00:00 2001 From: James Smith <james.smith9...@gmail.com> Date: Thu, 28 Sep 2023 10:11:15 -0400 Subject: [PATCH 1/6] [libc++] Implement ranges::iota and ranges::out_value_result --- libcxx/include/CMakeLists.txt | 2 + libcxx/include/__algorithm/out_value_result.h | 52 +++++++++ libcxx/include/__numeric/ranges_iota.h | 53 +++++++++ libcxx/include/algorithm | 4 + libcxx/include/numeric | 1 + libcxx/include/version | 2 +- .../out_value_result.pass.cpp | 102 ++++++++++++++++++ .../numeric.iota/ranges.iota.pass.cpp | 52 +++++++++ 8 files changed, 267 insertions(+), 1 deletion(-) create mode 100644 libcxx/include/__algorithm/out_value_result.h create mode 100644 libcxx/include/__numeric/ranges_iota.h create mode 100644 libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp create mode 100644 libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 2ec755236dbaee2..c6eb03f1d68e984 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -63,6 +63,7 @@ set(files __algorithm/next_permutation.h __algorithm/none_of.h __algorithm/nth_element.h + __algorithm/out_value_result.h __algorithm/partial_sort.h __algorithm/partial_sort_copy.h __algorithm/partition.h @@ -561,6 +562,7 @@ set(files __numeric/partial_sum.h __numeric/pstl_reduce.h __numeric/pstl_transform_reduce.h + __numeric/ranges_iota.h __numeric/reduce.h __numeric/transform_exclusive_scan.h __numeric/transform_inclusive_scan.h diff --git a/libcxx/include/__algorithm/out_value_result.h b/libcxx/include/__algorithm/out_value_result.h new file mode 100644 index 000000000000000..8baffec7b9ef4da --- /dev/null +++ b/libcxx/include/__algorithm/out_value_result.h @@ -0,0 +1,52 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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_OUT_VALUE_RESULT_H +#define _LIBCPP___ALGORITHM_OUT_VALUE_RESULT_H + +#include <__concepts/convertible_to.h> +#include <__config> +#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 { + +template <class _OutIter1, class _ValType1> +struct out_value_result { + _LIBCPP_NO_UNIQUE_ADDRESS _OutIter1 out; + _LIBCPP_NO_UNIQUE_ADDRESS _ValType1 value; + + template <class _OutIter2, class _ValType2> + requires convertible_to<const _OutIter1&, _OutIter2> && convertible_to<const _ValType1&, _ValType2> + constexpr operator out_value_result<_OutIter2, _ValType2>() const& { return {out, value}; } + + template <class _OutIter2, class _ValType2> + requires convertible_to<_OutIter1, _OutIter2> && convertible_to<_ValType1, _ValType2> + constexpr operator out_value_result<_OutIter2, _ValType2>() && { return {std::move(out), std::move(value)}; } +}; + +} // namespace ranges + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___ALGORITHM_OUT_VALUE_RESULT_H diff --git a/libcxx/include/__numeric/ranges_iota.h b/libcxx/include/__numeric/ranges_iota.h new file mode 100644 index 000000000000000..20311a68c2a348c --- /dev/null +++ b/libcxx/include/__numeric/ranges_iota.h @@ -0,0 +1,53 @@ +// -*- C++ -*- +//===----------------------------------------------------------------------===// +// +// 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___NUMERIC_RANGES_IOTA_H +#define _LIBCPP___NUMERIC_RANGES_IOTA_H + +#include <__algorithm/out_value_result.h> +#include <__config> +#include <__ranges/concepts.h> + +#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) +# pragma GCC system_header +#endif + +_LIBCPP_BEGIN_NAMESPACE_STD + +#if _LIBCPP_STD_VER >= 23 +namespace ranges { +template <typename _Out, typename _Tp> +using iota_result = ranges::out_value_result<_Out, _Tp>; + +struct __iota_fn { + template <input_or_output_iterator _Out, sentinel_for<_Out> _Sent, weakly_incrementable _Tp> + requires indirectly_writable<_Out, const _Tp&> + constexpr iota_result<_Out, _Tp> operator()(_Out __first, _Sent __last, _Tp __value) const { + while (__first != __last) { + *__first = static_cast<const _Tp&>(__value); + ++__first; + ++__value; + } + return {std::move(__first), std::move(__value)}; + } + + template <weakly_incrementable _Tp, ranges::output_range<const _Tp&> _Range> + constexpr iota_result<ranges::borrowed_iterator_t<_Range>, _Tp> operator()(_Range&& __r, _Tp __value) const { + return (*this)(ranges::begin(__r), ranges::end(__r), std::move(__value)); + } +}; + +inline constexpr __iota_fn iota{}; +} // namespace ranges + +#endif // _LIBCPP_STD_VER >= 23 + +_LIBCPP_END_NAMESPACE_STD + +#endif // _LIBCPP___NUMERIC_RANGES_IOTA_H diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm index 69ba9537dda6984..42f01a99a02cc93 100644 --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -42,6 +42,9 @@ namespace ranges { template <class I> struct in_found_result; // since C++20 + template <class O, class T> + struct out_value_result; // since C++23 + template<forward_iterator I, sentinel_for<I> S, class Proj = identity, indirect_strict_weak_order<projected<I, Proj>> Comp = ranges::less> // since C++20 constexpr I min_element(I first, S last, Comp comp = {}, Proj proj = {}); @@ -1817,6 +1820,7 @@ template <class BidirectionalIterator, class Compare> #include <__algorithm/next_permutation.h> #include <__algorithm/none_of.h> #include <__algorithm/nth_element.h> +#include <__algorithm/out_value_result.h> #include <__algorithm/partial_sort.h> #include <__algorithm/partial_sort_copy.h> #include <__algorithm/partition.h> diff --git a/libcxx/include/numeric b/libcxx/include/numeric index 3fcf6cefdb4b884..363dfd229df54dc 100644 --- a/libcxx/include/numeric +++ b/libcxx/include/numeric @@ -160,6 +160,7 @@ template<class T> #include <__numeric/partial_sum.h> #include <__numeric/pstl_reduce.h> #include <__numeric/pstl_transform_reduce.h> +#include <__numeric/ranges_iota.h> #include <__numeric/reduce.h> #include <__numeric/transform_exclusive_scan.h> #include <__numeric/transform_inclusive_scan.h> diff --git a/libcxx/include/version b/libcxx/include/version index e5a995366a7aa48..164c60dfd6af6d4 100644 --- a/libcxx/include/version +++ b/libcxx/include/version @@ -436,7 +436,7 @@ __cpp_lib_within_lifetime 202306L <type_traits> # define __cpp_lib_ranges_as_rvalue 202207L // # define __cpp_lib_ranges_chunk 202202L # define __cpp_lib_ranges_chunk_by 202202L -// # define __cpp_lib_ranges_iota 202202L +# define __cpp_lib_ranges_iota 202202L // # define __cpp_lib_ranges_join_with 202202L # define __cpp_lib_ranges_repeat 202207L // # define __cpp_lib_ranges_slide 202202L diff --git a/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp b/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp new file mode 100644 index 000000000000000..189149b0b4acb0c --- /dev/null +++ b/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp @@ -0,0 +1,102 @@ +//===----------------------------------------------------------------------===// +// +// 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 <class O, class T> +// struct out_value_result; + +#include <algorithm> +#include <cassert> +#include <type_traits> + +#include "MoveOnly.h" + +struct A { + explicit A(int); +}; +// no implicit conversion +static_assert(!std::is_constructible_v<std::ranges::out_value_result<A, A>, std::ranges::out_value_result<int, int>>); + +struct B { + B(int); +}; +// implicit conversion +static_assert(std::is_constructible_v<std::ranges::out_value_result<B, B>, std::ranges::out_value_result<int, int>>); +static_assert(std::is_constructible_v<std::ranges::out_value_result<B, B>, std::ranges::out_value_result<int, int>&>); +static_assert( + std::is_constructible_v<std::ranges::out_value_result<B, B>, const std::ranges::out_value_result<int, int>>); +static_assert( + std::is_constructible_v<std::ranges::out_value_result<B, B>, const std::ranges::out_value_result<int, int>&>); + +struct C { + C(int&); +}; +static_assert(!std::is_constructible_v<std::ranges::out_value_result<C, C>, std::ranges::out_value_result<int, int>&>); + +// has to be convertible via const& +static_assert( + std::is_convertible_v<std::ranges::out_value_result<int, int>&, std::ranges::out_value_result<long, long>>); +static_assert( + std::is_convertible_v<const std::ranges::out_value_result<int, int>&, std::ranges::out_value_result<long, long>>); +static_assert( + std::is_convertible_v<std::ranges::out_value_result<int, int>&&, std::ranges::out_value_result<long, long>>); +static_assert( + std::is_convertible_v<const std::ranges::out_value_result<int, int>&&, std::ranges::out_value_result<long, long>>); + +// should be move constructible +static_assert(std::is_move_constructible_v<std::ranges::out_value_result<MoveOnly, int>>); +static_assert(std::is_move_constructible_v<std::ranges::out_value_result<int, MoveOnly>>); + +struct NotConvertible {}; +// conversions should not work if there is no conversion +static_assert(!std::is_convertible_v<std::ranges::out_value_result<NotConvertible, int>, + std::ranges::out_value_result<int, NotConvertible>>); +static_assert(!std::is_convertible_v<std::ranges::out_value_result<int, NotConvertible>, + std::ranges::out_value_result<NotConvertible, int>>); + +template <class T> +struct ConvertibleFrom { + constexpr ConvertibleFrom(T c) : content{c} {} + T content; +}; + +constexpr bool test() { + { + std::ranges::out_value_result<double, int> res{10, 1}; + assert(res.out == 10); + assert(res.value == 1); + std::ranges::out_value_result<ConvertibleFrom<double>, ConvertibleFrom<int>> res2 = res; + assert(res2.out.content == 10); + assert(res2.value.content == 1); + } + { + std::ranges::out_value_result<MoveOnly, int> res{MoveOnly{}, 10}; + assert(res.out.get() == 1); + assert(res.value == 10); + auto res2 = std::move(res); + assert(res.out.get() == 0); + assert(res.value == 10); + assert(res2.out.get() == 1); + assert(res2.value == 10); + } + { + auto [min, max] = std::ranges::out_value_result<int, int>{1, 2}; + assert(min == 1); + assert(max == 2); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp new file mode 100644 index 000000000000000..1e2a6970adb35e3 --- /dev/null +++ b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp @@ -0,0 +1,52 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +// Testing std::ranges::iota + +// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 + +#include <cassert> +#include <numeric> +#include <array> + +#include "test_macros.h" +#include "test_iterators.h" + +// This is pulled directly from the std::iota test +template <class InIter> +TEST_CONSTEXPR_CXX20 void test0() { + int ia[] = {1, 2, 3, 4, 5}; + int ir[] = {5, 6, 7, 8, 9}; + const unsigned s = sizeof(ia) / sizeof(ia[0]); + std::ranges::iota(InIter(ia), InIter(ia + s), 5); + for (unsigned i = 0; i < s; ++i) + assert(ia[i] == ir[i]); +} + +TEST_CONSTEXPR_CXX20 bool test0() { + test0<forward_iterator<int*> >(); + test0<bidirectional_iterator<int*> >(); + test0<random_access_iterator<int*> >(); + test0<int*>(); + + return true; +} + +TEST_CONSTEXPR_CXX20 void test1() { + std::array<int, 5> ia = {1, 2, 3, 4, 5}; + std::array<int, 5> ir = {5, 6, 7, 8, 9}; + std::ranges::iota(ia, 5); + for (unsigned i = 0; i < ir.size(); ++i) + assert(ia[i] == ir[i]); +} + +int main(int, char**) { + test0(); + test1(); + return 0; +} \ No newline at end of file >From 574c94ce4f7eed06013a5e5d1460da1d148cc7cd Mon Sep 17 00:00:00 2001 From: James Smith <james.smith9...@gmail.com> Date: Fri, 29 Sep 2023 14:11:49 -0400 Subject: [PATCH 2/6] [libc++] Implement ranges::iota: update module and docs info --- libcxx/docs/FeatureTestMacroTable.rst | 2 +- libcxx/include/module.modulemap.in | 1 + libcxx/modules/std/numeric.inc | 8 ++++++-- libcxx/utils/generate_feature_test_macro_components.py | 1 - 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst index e1b4172b22c53da..ebb18dc7bed9bd2 100644 --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -348,7 +348,7 @@ Status --------------------------------------------------- ----------------- ``__cpp_lib_ranges_chunk_by`` ``202202L`` --------------------------------------------------- ----------------- - ``__cpp_lib_ranges_iota`` *unimplemented* + ``__cpp_lib_ranges_iota`` ``202202L`` --------------------------------------------------- ----------------- ``__cpp_lib_ranges_join_with`` *unimplemented* --------------------------------------------------- ----------------- diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 6d9bb8653fcb5e9..b092cc7c0e93bf1 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1602,6 +1602,7 @@ module std_private_numeric_pstl_transform_reduce [system] { export * } module std_private_numeric_reduce [system] { header "__numeric/reduce.h" } +module std_private_numeric_ranges_iota [system] { header "__numeric/ranges_iota.h" } module std_private_numeric_transform_exclusive_scan [system] { header "__numeric/transform_exclusive_scan.h" } module std_private_numeric_transform_inclusive_scan [system] { header "__numeric/transform_inclusive_scan.h" } module std_private_numeric_transform_reduce [system] { header "__numeric/transform_reduce.h" } diff --git a/libcxx/modules/std/numeric.inc b/libcxx/modules/std/numeric.inc index d2b7688d4e5f10a..9204e8d8715935d 100644 --- a/libcxx/modules/std/numeric.inc +++ b/libcxx/modules/std/numeric.inc @@ -42,8 +42,12 @@ export namespace std { using std::iota; namespace ranges { - // using std::ranges::iota_result; - // using std::ranges::iota; + +#if _LIBCPP_STD_VER >= 23 + using std::ranges::iota; + using std::ranges::iota_result; +#endif // _LIBCPP_STD_VER >= 23 + } // namespace ranges // [numeric.ops.gcd], greatest common divisor diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py index ac342aff0beb701..9fd50d66edbe317 100755 --- a/libcxx/utils/generate_feature_test_macro_components.py +++ b/libcxx/utils/generate_feature_test_macro_components.py @@ -806,7 +806,6 @@ def add_version_header(tc): "name": "__cpp_lib_ranges_iota", "values": {"c++23": 202202}, "headers": ["numeric"], - "unimplemented": True, }, { "name": "__cpp_lib_ranges_join_with", >From e4cfe00cb1b348d5a3515ef907c7c87334c6824c Mon Sep 17 00:00:00 2001 From: James Smith <james.smith9...@gmail.com> Date: Fri, 29 Sep 2023 16:50:46 -0400 Subject: [PATCH 3/6] [libc++] Implement ranges::iota: fixing formatting issues for out_value_result --- libcxx/include/__algorithm/out_value_result.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/libcxx/include/__algorithm/out_value_result.h b/libcxx/include/__algorithm/out_value_result.h index 8baffec7b9ef4da..a99d206f7035ff2 100644 --- a/libcxx/include/__algorithm/out_value_result.h +++ b/libcxx/include/__algorithm/out_value_result.h @@ -34,11 +34,15 @@ struct out_value_result { template <class _OutIter2, class _ValType2> requires convertible_to<const _OutIter1&, _OutIter2> && convertible_to<const _ValType1&, _ValType2> - constexpr operator out_value_result<_OutIter2, _ValType2>() const& { return {out, value}; } + constexpr operator out_value_result<_OutIter2, _ValType2>() const& { + return {out, value}; + } template <class _OutIter2, class _ValType2> requires convertible_to<_OutIter1, _OutIter2> && convertible_to<_ValType1, _ValType2> - constexpr operator out_value_result<_OutIter2, _ValType2>() && { return {std::move(out), std::move(value)}; } + constexpr operator out_value_result<_OutIter2, _ValType2>() && { + return {std::move(out), std::move(value)}; + } }; } // namespace ranges >From 7cdfea2d5acf553345ee0c506ce9e3914cbb3ed0 Mon Sep 17 00:00:00 2001 From: James Smith <james.smith9...@gmail.com> Date: Sat, 7 Oct 2023 12:04:32 -0400 Subject: [PATCH 4/6] [libc++] Implement ranges::iota: Cleaning up tests for ranges::iota and out_value_result. Updating Cxx23Papers.csv too. --- libcxx/docs/Status/Cxx23Papers.csv | 2 +- libcxx/include/__numeric/ranges_iota.h | 3 +- libcxx/modules/std/algorithm.inc | 2 +- .../out_value_result.pass.cpp | 104 ++++++++++------ .../numeric.iota/ranges.iota.pass.cpp | 117 ++++++++++++++++-- 5 files changed, 171 insertions(+), 57 deletions(-) diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index 9cb49fd5176ea5f..4618908cfb73941 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -46,7 +46,7 @@ "`P2255R2 <https://wg21.link/P2255R2>`__","LWG","A type trait to detect reference binding to temporary","February 2022","","" "`P2273R3 <https://wg21.link/P2273R3>`__","LWG","Making ``std::unique_ptr`` constexpr","February 2022","|Complete|","16.0" "`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","","","|ranges|" -"`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|" +"`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","|In progress|","","|ranges|" "`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","","","|ranges|" "`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|" "`P2443R1 <https://wg21.link/P2443R1>`__","LWG","``views::chunk_by``","February 2022","|Complete|","18.0","|ranges|" diff --git a/libcxx/include/__numeric/ranges_iota.h b/libcxx/include/__numeric/ranges_iota.h index 20311a68c2a348c..a8c069832da7e6d 100644 --- a/libcxx/include/__numeric/ranges_iota.h +++ b/libcxx/include/__numeric/ranges_iota.h @@ -13,6 +13,7 @@ #include <__algorithm/out_value_result.h> #include <__config> #include <__ranges/concepts.h> +#include <__utility/as_const.h> #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header @@ -30,7 +31,7 @@ struct __iota_fn { requires indirectly_writable<_Out, const _Tp&> constexpr iota_result<_Out, _Tp> operator()(_Out __first, _Sent __last, _Tp __value) const { while (__first != __last) { - *__first = static_cast<const _Tp&>(__value); + *__first = std::as_const(__value); ++__first; ++__value; } diff --git a/libcxx/modules/std/algorithm.inc b/libcxx/modules/std/algorithm.inc index b7900d15c10c2b5..62ce8cf02c14f95 100644 --- a/libcxx/modules/std/algorithm.inc +++ b/libcxx/modules/std/algorithm.inc @@ -18,7 +18,7 @@ export namespace std { using std::ranges::in_out_result; // using std::ranges::in_value_result; using std::ranges::min_max_result; - // using std::ranges::out_value_result; + using std::ranges::out_value_result; } // namespace ranges // [alg.nonmodifying], non-modifying sequence operations diff --git a/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp b/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp index 189149b0b4acb0c..3a930bb063f975f 100644 --- a/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp +++ b/libcxx/test/std/algorithms/algorithms.results/out_value_result.pass.cpp @@ -17,48 +17,25 @@ #include "MoveOnly.h" -struct A { - explicit A(int); +// +// Helper structs +// + +// only explicit construction +struct IterTypeExplicit { + explicit IterTypeExplicit(int*); }; -// no implicit conversion -static_assert(!std::is_constructible_v<std::ranges::out_value_result<A, A>, std::ranges::out_value_result<int, int>>); -struct B { - B(int); +// implicit construction +struct IterTypeImplicit { + IterTypeImplicit(int*); }; -// implicit conversion -static_assert(std::is_constructible_v<std::ranges::out_value_result<B, B>, std::ranges::out_value_result<int, int>>); -static_assert(std::is_constructible_v<std::ranges::out_value_result<B, B>, std::ranges::out_value_result<int, int>&>); -static_assert( - std::is_constructible_v<std::ranges::out_value_result<B, B>, const std::ranges::out_value_result<int, int>>); -static_assert( - std::is_constructible_v<std::ranges::out_value_result<B, B>, const std::ranges::out_value_result<int, int>&>); - -struct C { - C(int&); + +struct IterTypeImplicitRef { + IterTypeImplicitRef(int&); }; -static_assert(!std::is_constructible_v<std::ranges::out_value_result<C, C>, std::ranges::out_value_result<int, int>&>); - -// has to be convertible via const& -static_assert( - std::is_convertible_v<std::ranges::out_value_result<int, int>&, std::ranges::out_value_result<long, long>>); -static_assert( - std::is_convertible_v<const std::ranges::out_value_result<int, int>&, std::ranges::out_value_result<long, long>>); -static_assert( - std::is_convertible_v<std::ranges::out_value_result<int, int>&&, std::ranges::out_value_result<long, long>>); -static_assert( - std::is_convertible_v<const std::ranges::out_value_result<int, int>&&, std::ranges::out_value_result<long, long>>); - -// should be move constructible -static_assert(std::is_move_constructible_v<std::ranges::out_value_result<MoveOnly, int>>); -static_assert(std::is_move_constructible_v<std::ranges::out_value_result<int, MoveOnly>>); struct NotConvertible {}; -// conversions should not work if there is no conversion -static_assert(!std::is_convertible_v<std::ranges::out_value_result<NotConvertible, int>, - std::ranges::out_value_result<int, NotConvertible>>); -static_assert(!std::is_convertible_v<std::ranges::out_value_result<int, NotConvertible>, - std::ranges::out_value_result<NotConvertible, int>>); template <class T> struct ConvertibleFrom { @@ -66,6 +43,51 @@ struct ConvertibleFrom { T content; }; +// +constexpr void test_constraints() { + // requires convertible_to<const _OutIter1&, _OutIter2> && convertible_to<const _ValType1&, _ValType2> + static_assert( + std::is_constructible_v<std::ranges::out_value_result<int*, int>, std::ranges::out_value_result<int*, int>>); + + // test failure when implicit conversion isn't allowed + static_assert(!std::is_constructible_v<std::ranges::out_value_result<IterTypeExplicit, int>, + std::ranges::out_value_result<int*, int>>); + + // test success when implicit conversion is allowed, checking combinations of value, reference, and const + static_assert(std::is_constructible_v<std::ranges::out_value_result<IterTypeImplicit, int>, + std::ranges::out_value_result<int*, int>>); + static_assert(std::is_constructible_v<std::ranges::out_value_result<IterTypeImplicit, int>, + std::ranges::out_value_result<int*, int> const>); + static_assert(std::is_constructible_v<std::ranges::out_value_result<IterTypeImplicit, int>, + std::ranges::out_value_result<int*, int>&>); + static_assert(std::is_constructible_v<std::ranges::out_value_result<IterTypeImplicit, int>, + std::ranges::out_value_result<int*, int> const&>); + + static_assert(!std::is_constructible_v<std::ranges::out_value_result<IterTypeImplicitRef, int>, + std::ranges::out_value_result<int, int>&>); + + // has to be convertible via const& + static_assert( + std::is_convertible_v<std::ranges::out_value_result<int, int>&, std::ranges::out_value_result<long, long>>); + static_assert( + std::is_convertible_v<const std::ranges::out_value_result<int, int>&, std::ranges::out_value_result<long, long>>); + static_assert( + std::is_convertible_v<std::ranges::out_value_result<int, int>&&, std::ranges::out_value_result<long, long>>); + static_assert(std::is_convertible_v<const std::ranges::out_value_result<int, int>&&, + std::ranges::out_value_result<long, long>>); + + // should be move constructible + static_assert(std::is_move_constructible_v<std::ranges::out_value_result<MoveOnly, int>>); + static_assert(std::is_move_constructible_v<std::ranges::out_value_result<int, MoveOnly>>); + + // conversions should not work if there is no conversion + static_assert(!std::is_convertible_v<std::ranges::out_value_result<NotConvertible, int>, + std::ranges::out_value_result<int, NotConvertible>>); + static_assert(!std::is_convertible_v<std::ranges::out_value_result<int, NotConvertible>, + std::ranges::out_value_result<NotConvertible, int>>); +} + +// Test results constexpr bool test() { { std::ranges::out_value_result<double, int> res{10, 1}; @@ -86,17 +108,17 @@ constexpr bool test() { assert(res2.value == 10); } { - auto [min, max] = std::ranges::out_value_result<int, int>{1, 2}; - assert(min == 1); - assert(max == 2); + auto [out, val] = std::ranges::out_value_result<int, int>{1, 2}; + assert(out == 1); + assert(val == 2); } return true; } int main(int, char**) { + test_constraints(); test(); static_assert(test()); - return 0; -} +} \ No newline at end of file diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp index 1e2a6970adb35e3..85f99942e97b2ac 100644 --- a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp +++ b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp @@ -12,41 +12,132 @@ #include <cassert> #include <numeric> +#include <algorithm> #include <array> #include "test_macros.h" #include "test_iterators.h" +#include "almost_satisfies_types.h" + +// Concepts to check different overloads of std::ranges::iota +template <class Iter = int*, class Sent = int*, class Value = int> +concept HasIotaIter = requires(Iter&& iter, Sent&& sent, Value&& val) { + std::ranges::iota(std::forward<Iter>(iter), std::forward<Sent>(sent), std::forward<Value>(val)); +}; + +template <class Range, class Value = int> +concept HasIotaRange = requires(Range&& range, Value&& val) { + std::ranges::iota(std::forward<Range>(range), std::forward<Value>(val)); +}; + +constexpr void test_constraints() { + // Test constraints of the iterator/sentinel overload + // ================================================== + static_assert(HasIotaIter<int*, int*, int>); + + // !input_or_output_iterator<O> + static_assert(!HasIotaIter<InputIteratorNotInputOrOutputIterator>); + + // !sentinel_for<S, O> + static_assert(!HasIotaIter<int*, SentinelForNotSemiregular>); + static_assert(!HasIotaIter<int*, SentinelForNotWeaklyEqualityComparableWith>); + + // !weakly_incrementable<T> + static_assert(!HasIotaIter<int*, int*, WeaklyIncrementableNotMovable>); + + // !indirectly writable <O, T> + static_assert(!HasIotaIter<OutputIteratorNotIndirectlyWritable, int*, int>); + + // Test constraints for the range overload + // ======================================= + static_assert(HasIotaRange<UncheckedRange<int*>, int>); + + // !weakly_incrementable<T> + static_assert(!HasIotaRange<UncheckedRange<int*>, WeaklyIncrementableNotMovable>); + + // !ranges::output_range<const _Tp&> + static_assert(!HasIotaRange<UncheckedRange<int*>, OutputIteratorNotIndirectlyWritable>); +} // This is pulled directly from the std::iota test -template <class InIter> -TEST_CONSTEXPR_CXX20 void test0() { +template <class InOutIter, class Sent = InOutIter> +constexpr void test0() { int ia[] = {1, 2, 3, 4, 5}; int ir[] = {5, 6, 7, 8, 9}; const unsigned s = sizeof(ia) / sizeof(ia[0]); - std::ranges::iota(InIter(ia), InIter(ia + s), 5); - for (unsigned i = 0; i < s; ++i) - assert(ia[i] == ir[i]); + std::ranges::iota(InOutIter(ia), InOutIter(ia + s), 5); + assert(std::ranges::equal(ia, ir)); } -TEST_CONSTEXPR_CXX20 bool test0() { +constexpr bool test0() { + // TODO why don't these work? + // test0<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>(); + // test0<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>(); + // test0<cpp17_output_iterator<int*>, sentinel_wrapper<cpp17_output_iterator<int*>>>(); + // test0<cpp20_output_iterator<int*>, sentinel_wrapper<cpp20_output_iterator<int*>>>(); test0<forward_iterator<int*> >(); test0<bidirectional_iterator<int*> >(); test0<random_access_iterator<int*> >(); + test0<contiguous_iterator<int*>>(); test0<int*>(); return true; } -TEST_CONSTEXPR_CXX20 void test1() { - std::array<int, 5> ia = {1, 2, 3, 4, 5}; - std::array<int, 5> ir = {5, 6, 7, 8, 9}; - std::ranges::iota(ia, 5); - for (unsigned i = 0; i < ir.size(); ++i) - assert(ia[i] == ir[i]); +template <class Iter, class Sent, std::size_t N> +constexpr void test_result(std::array<int, N> input, int starting_value, std::array<int, N> const expected) { + { // (iterator, sentinel) overload + auto in_begin = Iter(input.data()); + auto in_end = Sent(Iter(input.data() + input.size())); + std::same_as<std::ranges::out_value_result<Iter, int>> decltype(auto) result = + std::ranges::iota(std::move(in_begin), std::move(in_end), starting_value); + assert(result.out == in_end); + if constexpr (expected.size() > 0) { + assert(result.value == expected.back() + 1); + } else { + assert(result.value == starting_value); + } + assert(std::ranges::equal(input, expected)); + } + + { // (range) overload + auto in_begin = Iter(input.data()); + auto in_end = Sent(Iter(input.data() + input.size())); + auto range = std::ranges::subrange(std::move(in_begin), std::move(in_end)); + + std::same_as<std::ranges::out_value_result<Iter, int>> decltype(auto) result = + std::ranges::iota(range, starting_value); + assert(result.out == in_end); + if constexpr (expected.size() > 0) { + assert(result.value == expected.back() + 1); + } else { + assert(result.value == starting_value); + } + assert(std::ranges::equal(input, expected)); + } +} + +constexpr bool test_results() { + using Iter = forward_iterator<int*>; + using Sent = sentinel_wrapper<Iter>; + + // Empty + test_result<Iter, Sent, 0>({}, 0, {}); + // 1-element sequence + test_result<Iter, Sent, 1>({1}, 0, {0}); + // Longer sequence + test_result<Iter, Sent, 5>({1, 2, 3, 4, 5}, 0, {0, 1, 2, 3, 4}); + + return true; } int main(int, char**) { + test_constraints(); + test0(); - test1(); + static_assert(test0()); + + test_results(); + static_assert(test_results()); return 0; } \ No newline at end of file >From a1d015c2affa40bb5f5313cdaed46c5a8cd4b243 Mon Sep 17 00:00:00 2001 From: James Smith <james.smith9...@gmail.com> Date: Sat, 7 Oct 2023 12:35:20 -0400 Subject: [PATCH 5/6] [libc++] Implement ranges::iota: Cleaning up tests for ranges::iota and adding testing for more iterators --- .../numeric.iota/ranges.iota.pass.cpp | 53 ++++++------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp index 85f99942e97b2ac..611293debbf731b 100644 --- a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp +++ b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp @@ -59,31 +59,6 @@ constexpr void test_constraints() { static_assert(!HasIotaRange<UncheckedRange<int*>, OutputIteratorNotIndirectlyWritable>); } -// This is pulled directly from the std::iota test -template <class InOutIter, class Sent = InOutIter> -constexpr void test0() { - int ia[] = {1, 2, 3, 4, 5}; - int ir[] = {5, 6, 7, 8, 9}; - const unsigned s = sizeof(ia) / sizeof(ia[0]); - std::ranges::iota(InOutIter(ia), InOutIter(ia + s), 5); - assert(std::ranges::equal(ia, ir)); -} - -constexpr bool test0() { - // TODO why don't these work? - // test0<cpp17_input_iterator<int*>, sentinel_wrapper<cpp17_input_iterator<int*>>>(); - // test0<cpp20_input_iterator<int*>, sentinel_wrapper<cpp20_input_iterator<int*>>>(); - // test0<cpp17_output_iterator<int*>, sentinel_wrapper<cpp17_output_iterator<int*>>>(); - // test0<cpp20_output_iterator<int*>, sentinel_wrapper<cpp20_output_iterator<int*>>>(); - test0<forward_iterator<int*> >(); - test0<bidirectional_iterator<int*> >(); - test0<random_access_iterator<int*> >(); - test0<contiguous_iterator<int*>>(); - test0<int*>(); - - return true; -} - template <class Iter, class Sent, std::size_t N> constexpr void test_result(std::array<int, N> input, int starting_value, std::array<int, N> const expected) { { // (iterator, sentinel) overload @@ -100,7 +75,10 @@ constexpr void test_result(std::array<int, N> input, int starting_value, std::ar assert(std::ranges::equal(input, expected)); } - { // (range) overload + // The range overload adds the additional constraint that it must be an outputrange + // so skip this for the input iterators we test + if constexpr (!std::is_same_v<Iter, cpp17_input_iterator<int*>> && + !std::is_same_v<Iter, cpp20_input_iterator<int*>>) { // (range) overload auto in_begin = Iter(input.data()); auto in_end = Sent(Iter(input.data() + input.size())); auto range = std::ranges::subrange(std::move(in_begin), std::move(in_end)); @@ -117,27 +95,30 @@ constexpr void test_result(std::array<int, N> input, int starting_value, std::ar } } -constexpr bool test_results() { - using Iter = forward_iterator<int*>; - using Sent = sentinel_wrapper<Iter>; - +template <class Iter, class Sent = sentinel_wrapper<Iter>> +constexpr void test_results() { // Empty test_result<Iter, Sent, 0>({}, 0, {}); // 1-element sequence test_result<Iter, Sent, 1>({1}, 0, {0}); // Longer sequence test_result<Iter, Sent, 5>({1, 2, 3, 4, 5}, 0, {0, 1, 2, 3, 4}); +} - return true; +void test_results() { + test_results<cpp17_input_iterator<int*>>(); + test_results<cpp20_input_iterator<int*>>(); + test_results<cpp17_output_iterator<int*>>(); + test_results<cpp20_output_iterator<int*>>(); + test_results<forward_iterator<int*>>(); + test_results<bidirectional_iterator<int*>>(); + test_results<random_access_iterator<int*>>(); + test_results<contiguous_iterator<int*>>(); + test_results<int*>(); } int main(int, char**) { test_constraints(); - - test0(); - static_assert(test0()); - test_results(); - static_assert(test_results()); return 0; } \ No newline at end of file >From ab4c67d3537379aab5117ac8dad232c5072991e9 Mon Sep 17 00:00:00 2001 From: James Smith <james.smith9...@gmail.com> Date: Sat, 7 Oct 2023 12:40:02 -0400 Subject: [PATCH 6/6] [libc++] Implement ranges::iota: Using clang-format-17 to make CI checks happy --- .../numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp index 611293debbf731b..3a7bc4319bebd6e 100644 --- a/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp +++ b/libcxx/test/std/numerics/numeric.ops/numeric.iota/ranges.iota.pass.cpp @@ -26,9 +26,8 @@ concept HasIotaIter = requires(Iter&& iter, Sent&& sent, Value&& val) { }; template <class Range, class Value = int> -concept HasIotaRange = requires(Range&& range, Value&& val) { - std::ranges::iota(std::forward<Range>(range), std::forward<Value>(val)); -}; +concept HasIotaRange = + requires(Range&& range, Value&& val) { std::ranges::iota(std::forward<Range>(range), std::forward<Value>(val)); }; constexpr void test_constraints() { // Test constraints of the iterator/sentinel overload _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits