diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst index ae48eaed1f46b..4eb15fa0eb131 100644 --- a/libcxx/docs/FeatureTestMacroTable.rst +++ b/libcxx/docs/FeatureTestMacroTable.rst @@ -506,7 +506,7 @@ Status ---------------------------------------------------------- ----------------- ``__cpp_lib_philox_engine`` *unimplemented* ---------------------------------------------------------- ----------------- - ``__cpp_lib_ranges_concat`` *unimplemented* + ``__cpp_lib_ranges_concat`` ``202403L`` ---------------------------------------------------------- ----------------- ``__cpp_lib_ranges_indices`` ``202506L`` ---------------------------------------------------------- ----------------- diff --git a/libcxx/docs/ReleaseNotes/23.rst b/libcxx/docs/ReleaseNotes/23.rst index 843f25dc7708a..e94897ffaf437 100644 --- a/libcxx/docs/ReleaseNotes/23.rst +++ b/libcxx/docs/ReleaseNotes/23.rst @@ -48,6 +48,7 @@ Implemented Papers - P2164R9: ``views::enumerate`` (`Github `__) - P2322R6: ``ranges::fold`` (`Github `__) - P4144R1: Remove ``span``'s ``initializer_list`` constructor for C++26 (`Github `__) +- P2542R8: ``views::concat`` (`Github `__) - P3383R3: ``mdspan.at()`` (`Github `__) - P3508R0: Wording for "constexpr for specialized memory algorithms" (`Github `__) diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv index e329e14f70ab3..20b0b93eff753 100644 --- a/libcxx/docs/Status/Cxx2cIssues.csv +++ b/libcxx/docs/Status/Cxx2cIssues.csv @@ -68,8 +68,8 @@ "`LWG4071 `__","``reference_wrapper`` comparisons are not SFINAE-friendly","2024-06 (St. Louis)","|Complete|","19","`#105345 `__","" "`LWG4074 `__","``compatible-joinable-ranges`` is underconstrained","2024-06 (St. Louis)","|Complete|","21","`#105346 `__","" "`LWG4076 `__","``concat_view`` should be freestanding","2024-06 (St. Louis)","","","`#105347 `__","" -"`LWG4079 `__","Missing Preconditions in ``concat_view::iterator``\`s conversion constructor","2024-06 (St. Louis)","","","`#105348 `__","" -"`LWG4082 `__","``views::concat(r)`` is well-formed when ``r`` is an ``output_range``","2024-06 (St. Louis)","","","`#105349 `__","" +"`LWG4079 `__","Missing Preconditions in ``concat_view::iterator``\`s conversion constructor","2024-06 (St. Louis)","|Complete|","23","`#105348 `__","" +"`LWG4082 `__","``views::concat(r)`` is well-formed when ``r`` is an ``output_range``","2024-06 (St. Louis)","|Complete|","23","`#105349 `__","" "`LWG4083 `__","``views::as_rvalue`` should reject non-input ranges","2024-06 (St. Louis)","|Complete|","22","`#105351 `__","" "`LWG4096 `__","``views::iota(views::iota(0))`` should be rejected","2024-06 (St. Louis)","|Complete|","22","`#105352 `__","" "`LWG4098 `__","``views::adjacent<0>`` should reject non-forward ranges","2024-06 (St. Louis)","","","`#105353 `__","" @@ -155,7 +155,7 @@ "`LWG4020 `__","``extents::index-cast`` weirdness","2025-11 (Kona)","|Complete|","23","`#171311 `__","" "`LWG4136 `__","Specify behavior of [linalg] Hermitian algorithms on diagonal with nonzero imaginary part","2025-11 (Kona)","","","`#171312 `__","" "`LWG4137 `__","Fix *Mandates*, *Preconditions*, and *Complexity* elements of [linalg] algorithms","2025-11 (Kona)","","","`#171313 `__","" -"`LWG4166 `__","``concat_view::end()`` should be more constrained in order to support noncopyable iterators","2025-11 (Kona)","","","`#171314 `__","" +"`LWG4166 `__","``concat_view::end()`` should be more constrained in order to support noncopyable iterators","2025-11 (Kona)","|Complete|","23","`#171314 `__","" "`LWG4230 `__","``simd::real/imag`` is overconstrained","2025-11 (Kona)","","","`#171316 `__","" "`LWG4243 `__","``as_bytes``/``as_writable_bytes`` is broken with ``span``","2025-11 (Kona)","","","`#171317 `__","" "`LWG4251 `__","Move assignment for ``indirect`` unnecessarily requires copy construction","2025-11 (Kona)","","","`#171318 `__","" diff --git a/libcxx/docs/Status/Cxx2cPapers.csv b/libcxx/docs/Status/Cxx2cPapers.csv index 5b229c75cc8c0..2132e80251657 100644 --- a/libcxx/docs/Status/Cxx2cPapers.csv +++ b/libcxx/docs/Status/Cxx2cPapers.csv @@ -54,7 +54,7 @@ "`P3142R0 `__","Printing Blank Lines with ``println``","2024-03 (Tokyo)","|Complete|","19","`#105415 `__","Implemented as a DR against C++23. (MSVC STL and libstdc++ will do the same.)" "`P2845R8 `__","Formatting of ``std::filesystem::path``","2024-03 (Tokyo)","","","`#105416 `__","" "`P0493R5 `__","Atomic minimum/maximum","2024-03 (Tokyo)","","","`#105418 `__","" -"`P2542R8 `__","``views::concat``","2024-03 (Tokyo)","","","`#105419 `__","" +"`P2542R8 `__","``views::concat``","2024-03 (Tokyo)","|Complete|","23","`#105419 `__","" "`P2591R5 `__","Concatenation of strings and string views","2024-03 (Tokyo)","|Complete|","19","`#105420 `__","" "`P2248R8 `__","Enabling list-initialization for algorithms","2024-03 (Tokyo)","","","`#105421 `__","" "`P2810R4 `__","``is_debugger_present`` ``is_replaceable``","2024-03 (Tokyo)","","","`#105422 `__","" diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt index 5e9040a62dd53..10dfb4b4d58cb 100644 --- a/libcxx/include/CMakeLists.txt +++ b/libcxx/include/CMakeLists.txt @@ -724,6 +724,7 @@ set(files __ranges/as_rvalue_view.h __ranges/chunk_by_view.h __ranges/common_view.h + __ranges/concat_view.h __ranges/concepts.h __ranges/container_compatible_range.h __ranges/counted.h diff --git a/libcxx/include/__ranges/concat_view.h b/libcxx/include/__ranges/concat_view.h new file mode 100644 index 0000000000000..3bbe9db12e0f8 --- /dev/null +++ b/libcxx/include/__ranges/concat_view.h @@ -0,0 +1,651 @@ +// -*- 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___RANGES_CONCAT_VIEW_H +#define _LIBCPP___RANGES_CONCAT_VIEW_H + +#include <__assert> +#include <__concepts/common_reference_with.h> +#include <__concepts/constructible.h> +#include <__concepts/convertible_to.h> +#include <__concepts/copyable.h> +#include <__concepts/derived_from.h> +#include <__concepts/equality_comparable.h> +#include <__concepts/swappable.h> +#include <__config> +#include <__iterator/concepts.h> +#include <__iterator/default_sentinel.h> +#include <__iterator/distance.h> +#include <__iterator/incrementable_traits.h> +#include <__iterator/iter_move.h> +#include <__iterator/iter_swap.h> +#include <__iterator/iterator_traits.h> +#include <__iterator/next.h> +#include <__ranges/access.h> +#include <__ranges/all.h> +#include <__ranges/concepts.h> +#include <__ranges/movable_box.h> +#include <__ranges/range_adaptor.h> +#include <__ranges/size.h> +#include <__ranges/view_interface.h> +#include <__tuple/tuple_transform.h> +#include <__type_traits/conditional.h> +#include <__type_traits/decay.h> +#include <__type_traits/is_nothrow_constructible.h> +#include <__type_traits/make_unsigned.h> +#include <__type_traits/maybe_const.h> +#include <__utility/forward.h> +#include <__utility/in_place.h> +#include <__utility/move.h> +#include +#include + +#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 >= 26 + +namespace ranges { + +# ifdef __cpp_pack_indexing +template +using __extract_last _LIBCPP_NODEBUG = _Tp...[sizeof...(_Tp) - 1]; +# else +template +struct __extract_last_impl : __extract_last_impl<_Tail...> {}; +template +struct __extract_last_impl<_Tp> { + using type _LIBCPP_NODEBUG = _Tp; +}; + +template +using __extract_last _LIBCPP_NODEBUG = __extract_last_impl<_Tp...>::type; +# endif + +template +struct __all_but_first_model_sized_range; + +template +struct __all_but_first_model_sized_range<_Const, _Head, _Tail...> { + static constexpr bool value = (sized_range<__maybe_const<_Const, _Tail>> && ...); +}; + +template +concept __all_random_access = (random_access_range<__maybe_const<_Const, _Views>> && ...); + +template +concept __all_bidirectional = (bidirectional_range<__maybe_const<_Const, _Views>> && ...); + +template +concept __all_forward = (forward_range<__maybe_const<_Const, _Views>> && ...); + +template +struct __all_common_ignore_last { + static constexpr bool value = + common_range<__maybe_const<_Const, _First>> && __all_common_ignore_last<_Const, _Tail...>::value; +}; + +template +struct __all_common_ignore_last<_Const, _Tail> { + static constexpr bool value = true; +}; + +template +concept __concat_is_random_access = + (__all_random_access<_Const, _Rs...>) && (__all_common_ignore_last<_Const, _Rs...>::value); + +template +concept __concat_is_bidirectional = + (__all_bidirectional<_Const, _Rs...>) && (__all_common_ignore_last<_Const, _Rs...>::value); + +template + requires((view<_Views> && ...) && (sizeof...(_Views) > 0) && __concatable<_Views...>) +class concat_view : public view_interface> { + tuple<_Views...> __views_; + + template + class __iterator; + +public: + _LIBCPP_HIDE_FROM_ABI constexpr concat_view() = default; + + _LIBCPP_HIDE_FROM_ABI constexpr explicit concat_view(_Views... __views) : __views_(std::move(__views)...) {} + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr __iterator begin() + requires(!(__simple_view<_Views> && ...)) + { + __iterator __it(this, in_place_index<0>, ranges::begin(std::get<0>(__views_))); + __it.template __satisfy<0>(); + return __it; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr __iterator begin() const + requires((range && ...) && __concatable) + { + __iterator __it(this, in_place_index<0>, ranges::begin(std::get<0>(__views_))); + __it.template __satisfy<0>(); + return __it; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto end() + requires(!(__simple_view<_Views> && ...)) + { + if constexpr (__all_forward && common_range<__maybe_const>>) { + constexpr auto __n = sizeof...(_Views); + return __iterator(this, in_place_index<__n - 1>, ranges::end(std::get<__n - 1>(__views_))); + } else { + return default_sentinel; + } + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto end() const + requires((range && ...) && __concatable) + { + if constexpr (__all_forward && common_range<__maybe_const>>) { + constexpr auto __n = sizeof...(_Views); + return __iterator(this, in_place_index<__n - 1>, ranges::end(std::get<__n - 1>(__views_))); + } else { + return default_sentinel; + } + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto size() + requires(sized_range<_Views> && ...) + { + return std::apply( + [](auto... __sizes) { return (make_unsigned_t>(__sizes) + ...); }, + std::__tuple_transform(ranges::size, __views_)); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto size() const + requires(sized_range && ...) + { + return std::apply( + [](auto... __sizes) { return (make_unsigned_t>(__sizes) + ...); }, + std::__tuple_transform(ranges::size, __views_)); + } +}; + +template +concat_view(_Views&&...) -> concat_view...>; + +template +struct __concat_view_iterator_category {}; + +template + requires __all_forward<_Const, _Views...> +struct __concat_view_iterator_category<_Const, _Views...> { +private: + constexpr static bool __derive_pack_random_iterator = + (derived_from>>::iterator_category, + random_access_iterator_tag> && + ...); + constexpr static bool __derive_pack_bidirectional_iterator = + (derived_from>>::iterator_category, + bidirectional_iterator_tag> && + ...); + constexpr static bool __derive_pack_forward_iterator = + (derived_from>>::iterator_category, + forward_iterator_tag> && + ...); + +public: + using iterator_category = + _If...>>, + input_iterator_tag, + _If<__derive_pack_random_iterator, + random_access_iterator_tag, + _If<__derive_pack_bidirectional_iterator, + bidirectional_iterator_tag, + _If<__derive_pack_forward_iterator, forward_iterator_tag, input_iterator_tag > > > >; +}; + +template + requires((view<_Views> && ...) && (sizeof...(_Views) > 0) && __concatable<_Views...>) +template +class concat_view<_Views...>::__iterator : public __concat_view_iterator_category<_Const, _Views...> { +public: + using iterator_concept = + _If<__concat_is_random_access<_Const, _Views...>, + random_access_iterator_tag, + _If<__concat_is_bidirectional<_Const, _Views...>, + bidirectional_iterator_tag, + _If< __all_forward<_Const, _Views...>, forward_iterator_tag, input_iterator_tag > > >; + using value_type = __concat_value_t<__maybe_const<_Const, _Views>...>; + using difference_type = common_type_t>...>; + +private: + using __base_iter _LIBCPP_NODEBUG = variant>...>; + __base_iter __it_; + __maybe_const<_Const, concat_view>* __parent_ = nullptr; + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __satisfy() { + if constexpr (_Idx < (sizeof...(_Views) - 1)) { + if (std::get<_Idx>(__it_) == ranges::end(std::get<_Idx>(__parent_->__views_))) { + __it_.template emplace<_Idx + 1>(ranges::begin(std::get<_Idx + 1>(__parent_->__views_))); + __satisfy<_Idx + 1>(); + } + } + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __prev() { + if constexpr (_Idx == 0) { + --std::get<0>(__it_); + } else { + if (std::get<_Idx>(__it_) == ranges::begin(std::get<_Idx>(__parent_->__views_))) { + __it_.template emplace<_Idx - 1>(ranges::end(std::get<_Idx - 1>(__parent_->__views_))); + __prev<_Idx - 1>(); + } else { + --std::get<_Idx>(__it_); + } + } + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __advance_fwd(difference_type __offset, difference_type __steps) { + using __underlying_diff_type = iter_difference_t>; + if constexpr (_Idx == sizeof...(_Views) - 1) { + std::get<_Idx>(__it_) += static_cast<__underlying_diff_type>(__steps); + } else { + auto __n_size = ranges::distance(std::get<_Idx>(__parent_->__views_)); + if (__offset + __steps < __n_size) { + std::get<_Idx>(__it_) += static_cast<__underlying_diff_type>(__steps); + } else { + __it_.template emplace<_Idx + 1>(ranges::begin(std::get<_Idx + 1>(__parent_->__views_))); + __advance_fwd<_Idx + 1>(0, __offset + __steps - __n_size); + } + } + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __advance_bwd(difference_type __offset, difference_type __steps) { + using __underlying_diff_type = iter_difference_t>; + if constexpr (_Idx == 0) { + std::get<_Idx>(__it_) -= static_cast<__underlying_diff_type>(__steps); + } else { + if (__offset >= __steps) { + std::get<_Idx>(__it_) -= static_cast<__underlying_diff_type>(__steps); + } else { + auto __prev_size = ranges::distance(std::get<_Idx - 1>(__parent_->__views_)); + __it_.template emplace<_Idx - 1>(ranges::end(std::get<_Idx - 1>(__parent_->__views_))); + __advance_bwd<_Idx - 1>(__prev_size, __steps - __offset); + } + } + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr auto __invoke_at_index(_Func&& __func) const { + // TODO(GCC 16): Just capture `this` when GCC PR113563 and PR121008 are fixed. + return [&__func, &__view_iter = *this](this auto&& __self) { + if (_Is == __view_iter.__it_.index()) { + return __func.template operator()<_Is>(); + } + if constexpr (_Is + 1 < sizeof...(_Views)) { + return __self.template operator()<_Is + 1>(); + } + __builtin_unreachable(); + }.template operator()<0>(); + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __apply_at_index(size_t __index, _Func&& __func, index_sequence<_Is...>) const { + ((__index == _Is ? (static_cast(__func(integral_constant{})), 0) : 0), ...); + } + + template + _LIBCPP_HIDE_FROM_ABI constexpr void __apply_at_index(size_t __index, _Func&& __func) const { + __apply_at_index(__index, std::forward<_Func>(__func), make_index_sequence<_Idx>{}); + } + + template + _LIBCPP_HIDE_FROM_ABI explicit constexpr __iterator(__maybe_const<_Const, concat_view>* __parent, _Args&&... __args) + requires constructible_from<__base_iter, _Args&&...> + : __it_(std::forward<_Args>(__args)...), __parent_(__parent) {} + + friend class concat_view; + friend class __iterator; + +public: + _LIBCPP_HIDE_FROM_ABI __iterator() = default; + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator __i) + requires _Const && (convertible_to, iterator_t> && ...) + : __it_([&__src = __i.__it_](size_t __idx, index_sequence<_Indices...>) -> __base_iter { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__src.valueless_by_exception(), "Trying to convert from a valueless iterator of concat_view."); + using __src_lref = decltype((__src)); + using __construction_fptr = __base_iter (*)(__src_lref); + static constexpr __construction_fptr __vtable[]{[](__src_lref __src_var) -> __base_iter { + return __base_iter(in_place_index<_Indices>, std::__unchecked_get<_Indices>(std::move(__src_var))); + }...}; + return __vtable[__idx](__src); + }(__i.__it_.index(), make_index_sequence>{})), + __parent_(__i.__parent_) {} + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator*() const { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it_.valueless_by_exception(), "Trying to dereference a valueless iterator of concat_view."); + return __variant_detail::__visitation::__variant::__visit_value( + [](auto&& __it) -> __concat_reference_t<__maybe_const<_Const, _Views>...> { return *__it; }, __it_); + } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it_.valueless_by_exception(), "Trying to increment a valueless iterator of concat_view."); + size_t __active_index = __it_.index(); + __apply_at_index>(__active_index, [&](auto __index_constant) { + constexpr size_t __i = __index_constant.value; + ++std::__unchecked_get<__i>(__it_); + __satisfy<__i>(); + }); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { ++*this; } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int) + requires(__all_forward<_Const, _Views...>) + { + auto __tmp = *this; + ++*this; + return __tmp; + } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--() + requires __concat_is_bidirectional<_Const, _Views...> + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it_.valueless_by_exception(), "Trying to decrement a valueless iterator of concat_view."); + size_t __active_index = __it_.index(); + __apply_at_index>(__active_index, [&](auto __index_constant) { + constexpr size_t __i = __index_constant.value; + __prev<__i>(); + }); + return *this; + } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int) + requires __concat_is_bidirectional<_Const, _Views...> + { + auto __tmp = *this; + --*this; + return __tmp; + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y) + requires(equality_comparable>> && ...) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view."); + return __x.__it_ == __y.__it_; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator[](difference_type __n) const + requires __concat_is_random_access<_Const, _Views...> + { + return *((*this) + __n); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(const __iterator& __it, difference_type __n) + requires __concat_is_random_access<_Const, _Views...> + { + auto __temp = __it; + __temp += __n; + return __temp; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(difference_type __n, const __iterator& __it) + requires __concat_is_random_access<_Const, _Views...> + { + return __it + __n; + } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator+=(difference_type __n) + requires __concat_is_random_access<_Const, _Views...> + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it_.valueless_by_exception(), "Trying to increment a valueless iterator of concat_view."); + size_t __active_index = __it_.index(); + if (__n > 0) { + __apply_at_index__views_)>>(__active_index, [&](auto __index_constant) { + constexpr size_t __i = __index_constant.value; + auto& __active_view = std::get<__i>(__parent_->__views_); + difference_type __idx = std::get<__i>(__it_) - ranges::begin(__active_view); + __advance_fwd<__i>(__idx, __n); + }); + + } + + else if (__n < 0) { + __apply_at_index__views_)>>(__active_index, [&](auto __index_constant) { + constexpr size_t __i = __index_constant.value; + auto& __active_view = std::get<__i>(__parent_->__views_); + difference_type __idx = std::get<__i>(__it_) - ranges::begin(__active_view); + __advance_bwd<__i>(__idx, -__n); + }); + } + + return *this; + } + + _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator-=(difference_type __n) + requires __concat_is_random_access<_Const, _Views...> + { + *this += -__n; + return *this; + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __it, default_sentinel_t) { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view with the default sentinel."); + constexpr auto __last_idx = sizeof...(_Views) - 1; + return __it.__it_.index() == __last_idx && + std::__unchecked_get<__last_idx>(__it.__it_) == ranges::end(std::get<__last_idx>(__it.__parent_->__views_)); + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator<(const __iterator& __x, const __iterator& __y) + requires(__all_random_access<_Const, _Views> && ...) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view."); + return __x.__it_ < __y.__it_; + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator>(const __iterator& __x, const __iterator& __y) + requires(__all_random_access<_Const, _Views> && ...) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view."); + return __x.__it_ > __y.__it_; + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator<=(const __iterator& __x, const __iterator& __y) + requires(__all_random_access<_Const, _Views> && ...) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view."); + return __x.__it_ <= __y.__it_; + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator>=(const __iterator& __x, const __iterator& __y) + requires(__all_random_access<_Const, _Views> && ...) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view."); + return __x.__it_ >= __y.__it_; + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr auto operator<=>(const __iterator& __x, const __iterator& __y) + requires((__all_random_access<_Const, _Views> && ...) && + (three_way_comparable>> && ...)) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS(!__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to compare a valueless iterator of concat_view."); + return __x.__it_ <=> __y.__it_; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr decltype(auto) iter_move(const __iterator& __it) noexcept( + + ((is_nothrow_invocable_v< decltype(ranges::iter_move), const iterator_t<__maybe_const<_Const, _Views>>& > && + is_nothrow_convertible_v< range_rvalue_reference_t<__maybe_const<_Const, _Views>>, + __concat_rvalue_reference_t<__maybe_const<_Const, _Views>...> >) && + ...)) + + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it.__it_.valueless_by_exception(), "Trying to apply iter_move to a valueless iterator of concat_view."); + return __variant_detail::__visitation::__variant::__visit_value( + [](const auto& __i) -> __concat_rvalue_reference_t<__maybe_const<_Const, _Views>...> { + return ranges::iter_move(__i); + }, + __it.__it_); + } + + _LIBCPP_HIDE_FROM_ABI friend constexpr void iter_swap(const __iterator& __x, const __iterator& __y) + + noexcept((noexcept(ranges::swap(*__x, *__y))) && + (noexcept(ranges::iter_swap(std::declval>>(), + std::declval>>())) && + ...)) + + requires swappable_with, iter_reference_t<__iterator>> && + (... && indirectly_swappable>>) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to swap iterators of concat_view where at least one iterator is valueless."); + __variant_detail::__visitation::__variant::__visit_value( + [&](const auto& __it1, const auto& __it2) { + if constexpr (is_same_v) { + ranges::iter_swap(__it1, __it2); + } else { + ranges::swap(*__x, *__y); + } + }, + __x.__it_, + __y.__it_); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type + operator-(const __iterator& __x, const __iterator& __y) + requires __concat_is_random_access<_Const, _Views...> + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__x.__it_.valueless_by_exception() && !__y.__it_.valueless_by_exception(), + "Trying to subtract two iterators of concat_view where at least one iterator is valueless."); + return __x.__invoke_at_index([&]() -> difference_type { + return __y.__invoke_at_index([&]() -> difference_type { + if constexpr (__index_x > __index_y) { + auto __dx = ranges::distance( + ranges::begin(std::get<__index_x>(__x.__parent_->__views_)), std::get<__index_x>(__x.__it_)); + auto __dy = ranges::distance( + std::get<__index_y>(__y.__it_), ranges::end(std::get<__index_y>(__y.__parent_->__views_))); + difference_type __s = [&](this auto&& __self) -> difference_type { + if constexpr (__start < __end) { + return ranges::size(std::get<__start>(__x.__parent_->__views_)) + + __self.template operator()<__start + 1, __end>(); + } + return 0; + }.template operator()<__index_y + 1, __index_x>(); + return __dy + __s + __dx; + } else if constexpr (__index_x < __index_y) { + return -(__y - __x); + } else { + return std::get<__index_x>(__x.__it_) - std::get<__index_y>(__y.__it_); + } + }); + }); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator-(const __iterator& __it, difference_type __n) + requires __concat_is_random_access<_Const, _Views...> + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__it.__it_.valueless_by_exception(), "Trying to subtract a valuess iterators of concat_view."); + auto __temp = __it; + __temp -= __n; + return __temp; + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type + operator-(const __iterator& __x, default_sentinel_t) + requires(sized_sentinel_for>, iterator_t<__maybe_const<_Const, _Views>>> && + ...) && + (__all_but_first_model_sized_range<_Const, _Views...>::value) + { + _LIBCPP_ASSERT_VALID_ELEMENT_ACCESS( + !__x.__it_.valueless_by_exception(), + "Trying to subtract a valuess iterators of concat_view from the default sentinel."); + return __x.__invoke_at_index([&]() -> difference_type { + auto __dx = + ranges::distance(std::get<__index_x>(__x.__it_), ranges::end(std::get<__index_x>(__x.__parent_->__views_))); + difference_type __s = [&](this auto&& __self) -> difference_type { + if constexpr (__start < __end) { + return ranges::size(std::get<__start>(__x.__parent_->__views_)) + + __self.template operator()<__start + 1, __end>(); + } + return 0; + }.template operator()<__index_x + 1, sizeof...(_Views)>(); + return -(__dx + __s); + }); + } + + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type + operator-(default_sentinel_t, const __iterator& __x) + requires(sized_sentinel_for>, iterator_t<__maybe_const<_Const, _Views>>> && + ...) && + (__all_but_first_model_sized_range<_Const, _Views...>::value) + { + return -(__x - default_sentinel); + } +}; + +namespace views { +namespace __concat { +struct __fn { + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto + operator()(_Range&& __range) noexcept(noexcept(views::all((std::forward<_Range>(__range))))) + -> decltype(views::all((std::forward<_Range>(__range)))) { + return views::all(std::forward<_Range>(__range)); + } + + template + [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto + operator()(_FirstRange&& __first, _TailRanges&&... __tail) noexcept( + noexcept(concat_view(std::forward<_FirstRange>(__first), std::forward<_TailRanges>(__tail)...))) + -> decltype(concat_view(std::forward<_FirstRange>(__first), std::forward<_TailRanges>(__tail)...)) { + return concat_view(std::forward<_FirstRange>(__first), std::forward<_TailRanges>(__tail)...); + } +}; +} // namespace __concat + +inline namespace __cpo { +inline constexpr auto concat = __concat::__fn{}; +} // namespace __cpo +} // namespace views + +} // namespace ranges + +#endif // _LIBCPP_STD_VER >= 26 + +_LIBCPP_END_NAMESPACE_STD + +_LIBCPP_POP_MACROS + +#endif // _LIBCPP___RANGES_CONCAT_VIEW_H diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in index 51a2b77a9ba45..2ccb455b7408d 100644 --- a/libcxx/include/module.modulemap.in +++ b/libcxx/include/module.modulemap.in @@ -1886,6 +1886,7 @@ module std [system] { export std.functional.bind_back } module common_view { header "__ranges/common_view.h" } + module concat_view { header "__ranges/concat_view.h" } module concepts { header "__ranges/concepts.h" } module container_compatible_range { header "__ranges/container_compatible_range.h" } module counted { diff --git a/libcxx/include/ranges b/libcxx/include/ranges index 308224427db60..de05c31744fcf 100644 --- a/libcxx/include/ranges +++ b/libcxx/include/ranges @@ -201,6 +201,16 @@ namespace std::ranges { inline constexpr unspecified filter = unspecified; } + // [range.concat], concat view + template + requires (view && ...) && (sizeof...(Views) > 0) && + concatable + class concat_view; + + namespace views { + inline constexpr unspecified concat = unspecified; + } + // [range.drop], drop view template class drop_view; @@ -514,6 +524,10 @@ namespace std { # include <__ranges/zip_view.h> # endif +# if _LIBCPP_STD_VER >= 26 +# include <__ranges/concat_view.h> +# endif + # include // standard-mandated includes diff --git a/libcxx/include/version b/libcxx/include/version index 1c683b67e5700..471eb3c104b53 100644 --- a/libcxx/include/version +++ b/libcxx/include/version @@ -614,7 +614,7 @@ __cpp_lib_void_t 201411L # undef __cpp_lib_out_ptr # define __cpp_lib_out_ptr 202311L // # define __cpp_lib_philox_engine 202406L -// # define __cpp_lib_ranges_concat 202403L +# define __cpp_lib_ranges_concat 202403L # define __cpp_lib_ranges_indices 202506L # define __cpp_lib_ratio 202306L // # define __cpp_lib_rcu 202306L diff --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc index 576f0650438e3..876942bdffbf5 100644 --- a/libcxx/modules/std/ranges.inc +++ b/libcxx/modules/std/ranges.inc @@ -181,6 +181,15 @@ export namespace std { } // namespace views #endif // _LIBCPP_STD_VER >= 23 +#if _LIBCPP_STD_VER >= 26 + // [range.concat.view], concat_view + using std::ranges::concat_view; + + namespace views { + using std::ranges::views::concat; + } // namespace views +#endif // _LIBCPP_STD_VER >= 26 + // [range.filter], filter view using std::ranges::filter_view; diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp new file mode 100644 index 0000000000000..8e0ff79e16a27 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/iterator.valueless_by_exception.pass.cpp @@ -0,0 +1,659 @@ +//===----------------------------------------------------------------------===// +// +// 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: has-unix-headers, libcpp-hardening-mode={{extensive|debug}} +// REQUIRES: std-at-least-c++26 +// UNSUPPORTED: libcpp-hardening-mode=none + +#include +#include +#include +#include + +#include "check_assertion.h" +#include "double_move_tracker.h" +#include "test_iterators.h" + +int val[] = {1, 2, 3}; + +bool flag = false; + +template +struct Iter; + +template +struct Iter { + using value_type = int; + using difference_type = std::ptrdiff_t; + using reference = int&; + using pointer = int*; + using iterator_category = std::random_access_iterator_tag; + using iterator_concept = std::random_access_iterator_tag; + +private: + int* ptr_ = nullptr; + + template + friend struct Iter; + +public: + Iter() = default; + Iter(int* ptr) : ptr_(ptr) {} + Iter(const Iter&) = default; + Iter(Iter&& other) : ptr_(other.ptr_) { + if (flag) + throw 5; + } + + Iter& operator=(const Iter&) = default; + Iter& operator=(Iter&& o) { + ptr_ = o.ptr_; + if (flag) + throw 5; + return *this; + } + + reference operator*() const { return *ptr_; } + pointer operator->() const { return ptr_; } + reference operator[](difference_type n) const { return ptr_[n]; } + + Iter& operator++() { + ++ptr_; + return *this; + } + Iter operator++(int) { + auto tmp = *this; + ++*this; + return tmp; + } + Iter& operator--() { + --ptr_; + return *this; + } + Iter operator--(int) { + auto tmp = *this; + --*this; + return tmp; + } + + Iter& operator+=(difference_type n) { + ptr_ += n; + return *this; + } + Iter& operator-=(difference_type n) { + ptr_ -= n; + return *this; + } + + template + friend Iter operator+(Iter it, difference_type n); + + template + friend Iter operator+(difference_type n, Iter it); + + template + friend Iter operator-(Iter it, difference_type n); + + template + friend difference_type operator-(Iter a, Iter b); + + friend bool operator==(Iter a, Iter b) { return a.ptr_ == b.ptr_; }; + friend bool operator<(Iter a, Iter b) { return a.ptr_ < b.ptr_; } + friend bool operator>(Iter a, Iter b) { return a.ptr_ > b.ptr_; } + friend bool operator<=(Iter a, Iter b) { return a.ptr_ <= b.ptr_; } + friend bool operator>=(Iter a, Iter b) { return a.ptr_ >= b.ptr_; } + friend auto operator<=>(Iter a, Iter b) { return a.ptr_ <=> b.ptr_; } +}; + +template +inline Iter operator+(Iter it, std::ptrdiff_t n) { + return Iter(it.ptr_ + n); +} + +template +inline Iter operator+(std::ptrdiff_t n, Iter it) { + return Iter(it.ptr_ + n); +} + +template +inline Iter operator-(Iter it, std::ptrdiff_t n) { + return Iter(it.ptr_ - n); +} + +template +inline std::ptrdiff_t operator-(Iter a, Iter b) { + return a.ptr_ - b.ptr_; +} + +template +struct Range : std::ranges::view_base { + using iterator = Iter; + using const_iterator = Iter; + using sentinel = sentinel_wrapper; + + int* data_; + std::size_t size_; + + Range() : data_(val), size_(4) {} + + Range(int* data, std::size_t size) : data_(data), size_(size) {} + + iterator begin() { return iterator(data_); } + iterator end() { return iterator(data_ + size_); } + + const_iterator begin() const { return const_iterator(data_); } + const_iterator end() const { return const_iterator(data_ + size_); } +}; + +template +struct NonSimpleIter { + using difference_type = ptrdiff_t; + using value_type = T; + + T* ptr_ = nullptr; + + NonSimpleIter() = default; + NonSimpleIter(T* ptr) : ptr_(ptr) {} + + template + requires(std::is_const_v && !std::is_const_v && + std::is_same_v, std::remove_const_t>) + NonSimpleIter(const NonSimpleIter& other) : ptr_(other.ptr_) {} + + NonSimpleIter(const NonSimpleIter&) = default; + NonSimpleIter(NonSimpleIter&& other) : ptr_(other.ptr_) { + if (flag) + throw 5; + } + + NonSimpleIter& operator=(const NonSimpleIter&) = default; + NonSimpleIter& operator=(NonSimpleIter&& o) { + ptr_ = o.ptr_; + if (flag) + throw 5; + return *this; + } + + T& operator*() const { return *ptr_; } + NonSimpleIter& operator++() { + ++ptr_; + return *this; + } + NonSimpleIter operator++(int) { + auto tmp = *this; + ++*this; + return tmp; + } + + friend bool operator==(NonSimpleIter, NonSimpleIter) = default; +}; + +template +struct NonSimpleRange : std::ranges::view_base { + NonSimpleIter begin() { return &val[0]; } + NonSimpleIter end() { return &val[3]; } + NonSimpleIter begin() const { return &val[0]; } + NonSimpleIter end() const { return &val[3]; } +}; + +static_assert(std::ranges::range>); +static_assert(std::ranges::sized_range>); + +int main() { + { + // valueless by exception test operator* + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([=] { (void)*iter1; }(), "Trying to dereference a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator== + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + auto iter3 = cv.begin(); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [=] { (void)(iter1 == iter3); }(), "Trying to compare a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator== with a sentinel + flag = false; + Range<0> r1; + Range<1> r2; + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([=] { (void)(iter1 == std::default_sentinel); }(), + "Trying to compare a valueless iterator of concat_view with the default sentinel."); + } + } + + { + // valueless by exception test operator-- + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { --iter1; }(), "Trying to decrement a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator--(int) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { iter1--; }(), "Trying to decrement a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator++(int) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { iter1++; }(), "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator++ + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { ++iter1; }(), "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator+= + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { iter1 += 1; }(), "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator-= + // this one eventually calls operator+= inside the function + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { iter1 -= 1; }(), "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator-= + // this one eventually calls operator+= inside the function + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { (void)iter1[1]; }(), "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator+(it, n) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 + 1; }(), + "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator+(n, it) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 + 1; }(), + "Trying to increment a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator-(it, default_sentinel) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 - std::default_sentinel_t{}; }(), + "Trying to subtract a valuess iterators of concat_view from the default sentinel."); + } + } + + { + // valueless by exception test operator-(default_sentinel, it) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = iter1 - std::default_sentinel_t{}; }(), + "Trying to subtract a valuess iterators of concat_view from the default sentinel."); + } + } + + { + // valueless by exception test operator> + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 > iter2); }(), "Trying to compare a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator>= + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 >= iter2); }(), "Trying to compare a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator< + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 < iter2); }(), "Trying to compare a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator<= + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 <= iter2); }(), "Trying to compare a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator<=> + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 <=> iter2); }(), "Trying to compare a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test operator- between two iterators + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 - iter2); }(), + "Trying to subtract two iterators of concat_view where at least one iterator is valueless."); + } + } + + { + // valueless by exception test operator- with a constant + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { (void)(iter1 - 1); }(), "Trying to subtract a valuess iterators of concat_view."); + } + } + + { + // valueless by exception test iter_move(it) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { [[maybe_unused]] auto iter3 = std::ranges::iter_move(iter1); }(), + "Trying to apply iter_move to a valueless iterator of concat_view."); + } + } + + { + // valueless by exception test iter_swap(iter1, iter2) + flag = false; + Range<0> r1; + Range<1> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE([&] { std::ranges::iter_swap(iter1, iter2); }(), + "Trying to swap iterators of concat_view where at least one iterator is valueless."); + } + } + + { + // valueless by exception test constructor + flag = false; + NonSimpleRange<0, int> r1; + NonSimpleRange<1, int> r2; + + auto cv = std::views::concat(r1, r2); + auto iter1 = cv.begin(); + auto iter2 = std::ranges::next(cv.begin(), 4); + flag = true; + using Iter = std::ranges::iterator_t; // iterator + using CIter = std::ranges::iterator_t; // iterator + + try { + iter1 = std::move(iter2); + assert(false); + } catch (...) { + TEST_LIBCPP_ASSERT_FAILURE( + [&] { [[maybe_unused]] CIter it3(iter1); }(), "Trying to convert from a valueless iterator of concat_view."); + } + } +} diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp new file mode 100644 index 0000000000000..b81e646d381c4 --- /dev/null +++ b/libcxx/test/libcxx/ranges/range.adaptors/range.concat/nodiscard.verify.cpp @@ -0,0 +1,70 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +// Test that concat_view and its iterator's functions are marked [[nodiscard]]. + +#include +#include +#include +#include + +void test() { + std::array a{1, 2, 3}; + std::vector b{4, 5, 6}; + + std::ranges::concat_view cv(a, b); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + cv.begin(); + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::as_const(cv).begin(); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + cv.end(); + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::as_const(cv).end(); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + cv.size(); + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::as_const(cv).size(); + + // [range.concat.iterator] + + auto it = cv.begin(); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + *it; + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + it[2]; + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + it + 1; + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + 1 + it; + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + it - 1; + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + it - it; + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + it - std::default_sentinel; + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::default_sentinel - it; + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + iter_move(it); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::views::concat(a); + + // expected-warning@+1 {{ignoring return value of function declared with 'nodiscard' attribute}} + std::views::concat(a, b); +} diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp index 6825f9675d459..670b0e664721a 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp @@ -447,17 +447,11 @@ # error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_ranges_concat -# error "__cpp_lib_ranges_concat should be defined in c++26" -# endif -# if __cpp_lib_ranges_concat != 202403L -# error "__cpp_lib_ranges_concat should have the value 202403L in c++26" -# endif -# else -# ifdef __cpp_lib_ranges_concat -# error "__cpp_lib_ranges_concat should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_ranges_concat +# error "__cpp_lib_ranges_concat should be defined in c++26" +# endif +# if __cpp_lib_ranges_concat != 202403L +# error "__cpp_lib_ranges_concat should have the value 202403L in c++26" # endif # ifndef __cpp_lib_ranges_enumerate diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp index dfee4b6d458db..2aa52a64c6cf0 100644 --- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp +++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp @@ -7691,17 +7691,11 @@ # error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26" # endif -# if !defined(_LIBCPP_VERSION) -# ifndef __cpp_lib_ranges_concat -# error "__cpp_lib_ranges_concat should be defined in c++26" -# endif -# if __cpp_lib_ranges_concat != 202403L -# error "__cpp_lib_ranges_concat should have the value 202403L in c++26" -# endif -# else -# ifdef __cpp_lib_ranges_concat -# error "__cpp_lib_ranges_concat should not be defined because it is unimplemented in libc++!" -# endif +# ifndef __cpp_lib_ranges_concat +# error "__cpp_lib_ranges_concat should be defined in c++26" +# endif +# if __cpp_lib_ranges_concat != 202403L +# error "__cpp_lib_ranges_concat should have the value 202403L in c++26" # endif # ifndef __cpp_lib_ranges_contains diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp index 9443ca6c90a30..d83ca7f2eabfc 100644 --- a/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp +++ b/libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp @@ -137,6 +137,6 @@ static_assert(test(std::views::zip, a, a)); #if TEST_STD_VER >= 26 // static_assert(test(std::views::cache_latest, a)); -// static_assert(test(std::views::concat, a, a)); +static_assert(test(std::views::concat, a, a)); // static_assert(test(std::views::to_input, a)); #endif diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp new file mode 100644 index 0000000000000..b0358856a07c8 --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.concat/adaptor.pass.cpp @@ -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 +// +//===----------------------------------------------------------------------===// + +// REQUIRES: std-at-least-c++26 + +// std::views::concat + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../range_adaptor_types.h" + +static_assert(!std::is_invocable_v); +static_assert(!std::is_invocable_v); +static_assert(std::is_invocable_v); +static_assert(std::is_invocable_v); +static_assert(std::is_invocable_v); +static_assert(!std::is_invocable_v); + +constexpr bool test() { + { + // single range + int buffer[8] = {1, 2, 3, 4, 5, 6, 7, 8}; + std::same_as(buffer))))> decltype(auto) v = + std::ranges::views::concat(SizedRandomAccessView{buffer}); + assert(std::ranges::size(v) == 8); + static_assert(std::is_same_v, int&>); + } + + { + // single view as output range will be rejected + // https://cplusplus.github.io/LWG/issue4082 + std::vector v{1, 2, 3}; + static_assert( + !std::is_invocable_v); + } + + { + // more than one ranges + int buffer[4] = {1, 2, 3, 4}; + std::array a{1, 2, 3}; + std::same_as>>> decltype(auto) v = + std::ranges::views::concat(NonSimpleCommonRandomAccessSized{buffer}, a); + assert(&(*v.begin()) == &(buffer[0])); + assert(&(*(v.begin() + 4)) == &(a[0])); + static_assert(std::is_same_v, int&>); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp new file mode 100644 index 0000000000000..0c6081fd70c4f --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.concat/begin.pass.cpp @@ -0,0 +1,119 @@ +//===----------------------------------------------------------------------===// +// +// 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 __iterator begin() +// requires(!(__simple_view<_Views> && ...)) + +// constexpr __iterator begin() const +// requires((range && ...) && __concatable) + +#include +#include +#include + +#include +#include "test_iterators.h" +#include "types.h" +#include "../range_adaptor_types.h" + +template +concept HasConstBegin = requires(const T& ct) { ct.begin(); }; + +template +concept HasBegin = requires(T& t) { t.begin(); }; + +template +concept HasConstAndNonConstBegin = HasConstBegin && requires(T& t, const T& ct) { + requires !std::same_as; +}; + +template +concept HasOnlyNonConstBegin = HasBegin && !HasConstBegin; + +template +concept HasOnlyConstBegin = HasConstBegin && !HasConstAndNonConstBegin; + +constexpr bool test() { + // check the case of simple view + { + int buffer[4] = {1, 2, 3, 4}; + std::ranges::concat_view v(SimpleCommon{buffer}, SimpleCommon{buffer}); + static_assert(std::is_same_v); + assert(v.begin() == std::as_const(v).begin()); + assert(*v.begin() == buffer[0]); + assert(*std::as_const(v).begin() == buffer[0]); + + using View = decltype(v); + static_assert(HasOnlyConstBegin); + static_assert(!HasOnlyNonConstBegin); + static_assert(!HasConstAndNonConstBegin); + } + + // not all underlying ranges model simple view + { + int buffer[4] = {1, 2, 3, 4}; + std::ranges::concat_view v(SimpleCommon{buffer}, NonSimpleNonCommon{buffer}); + static_assert(!std::is_same_v); + assert(v.begin() == std::as_const(v).begin()); + assert(*v.begin() == buffer[0]); + assert(*std::as_const(v).begin() == buffer[0]); + + using View = decltype(v); + static_assert(!HasOnlyConstBegin); + static_assert(!HasOnlyNonConstBegin); + static_assert(HasConstAndNonConstBegin); + } + + // first view is empty + { + std::vector v1; + std::vector v2 = {1, 2, 3, 4}; + std::ranges::concat_view view(v1, v2); + auto it = view.begin(); + assert(*it == 1); + assert(it + 4 == view.end()); + } + + // first few views is empty, including different types + { + std::vector v1; + std::array v2; + std::vector v3 = {1, 2, 3, 4}; + std::ranges::concat_view view(v1, v2, v3); + auto it = view.begin(); + assert(*it == 1); + assert(it + 4 == view.end()); + } + + // all views are empty + { + std::array arr; + std::vector v; + std::ranges::concat_view(arr, v); + assert(v.begin() == v.end()); + } + + // testing concatable constraint + { + static_assert(!ConcatableConstViews); + static_assert(ConcatableConstViews); + static_assert(!ConcatableConstViews); + static_assert(!ConcatableConstViews); + static_assert(ConcatableConstViews); + } + + return true; +} + +int main(int, char**) { + test(); + static_assert(test()); + return 0; +} diff --git a/libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp new file mode 100644 index 0000000000000..0792c8c7c921a --- /dev/null +++ b/libcxx/test/std/ranges/range.adaptors/range.concat/constraints.pass.cpp @@ -0,0 +1,288 @@ +//===----------------------------------------------------------------------===// +// +// 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 + +#include +#include +#include +#include +#include +#include +#include "test_iterators.h" +#include "test_macros.h" + +// test concept constraints + +template +concept WellFormedView = requires(T& a) { std::views::concat(a); }; + +struct X {}; +struct Y {}; + +struct BadIter { + using value_type = int; + int* p; + + BadIter() = default; + explicit BadIter(int* q) : p(q) {} + + int& operator*() const { return *p; } + BadIter& operator++() { + ++p; + return *this; + } + void operator++(int) { ++p; } + + friend bool operator==(const BadIter& a, const BadIter& b) { return a.p == b.p; } + friend bool operator!=(const BadIter& a, const BadIter& b) { return !(a == b); } + + friend X iter_move(const BadIter&) { return X{}; } +}; + +struct BadView : std::ranges::view_base { + int buf_[1] = {0}; + BadIter begin() const { return BadIter(const_cast(buf_)); } + BadIter end() const { return BadIter(const_cast(buf_ + 1)); } +}; + +struct InputRange { + using Iterator = cpp17_input_iterator; + using Sentinel = sentinel_wrapper; + constexpr InputRange(int* b, int* e) : begin_(b), end_(e) {} + constexpr Iterator begin() { return Iterator(begin_); } + constexpr Sentinel end() { return Sentinel(Iterator(end_)); } + +private: + int* begin_; + int* end_; +}; + +struct RefOnlyRange1 : std::ranges::view_base { + X* begin() const; + X* end() const; +}; + +struct RefOnlyRange2 : std::ranges::view_base { + Y* begin() const; + Y* end() const; +}; + +namespace std { +template