[libc++] Implement ranges::move{, _backward}

This patch also adds a new optimization to `std::move`. It unwraps three `reverse_iterator`s if the wrapped iterator is a `contiguous_iterator` and the iterated type is trivially_movable. This allows us to simplify `ranges::move_backward` to a forward to `std::move` without any pessimization.

Reviewed By: var-const, #libc

Spies: libcxx-commits, mgorny

Differential Revision: https://reviews.llvm.org/D126616
This commit is contained in:
Nikolas Klauser 2022-06-23 12:23:41 +02:00
parent ea38744372
commit 2c3bbac0c7
10 changed files with 785 additions and 45 deletions

View File

@ -99,6 +99,8 @@ set(files
__algorithm/ranges_minmax.h
__algorithm/ranges_minmax_element.h
__algorithm/ranges_mismatch.h
__algorithm/ranges_move.h
__algorithm/ranges_move_backward.h
__algorithm/ranges_none_of.h
__algorithm/ranges_replace.h
__algorithm/ranges_replace_if.h

View File

@ -11,7 +11,10 @@
#include <__algorithm/unwrap_iter.h>
#include <__config>
#include <__iterator/iterator_traits.h>
#include <__iterator/reverse_iterator.h>
#include <__utility/move.h>
#include <__utility/pair.h>
#include <cstring>
#include <type_traits>
@ -23,53 +26,88 @@ _LIBCPP_BEGIN_NAMESPACE_STD
// move
template <class _InputIterator, class _OutputIterator>
template <class _InIter, class _Sent, class _OutIter>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX14
_OutputIterator
__move_constexpr(_InputIterator __first, _InputIterator __last, _OutputIterator __result)
{
for (; __first != __last; ++__first, (void) ++__result)
*__result = _VSTD::move(*__first);
return __result;
pair<_InIter, _OutIter> __move_impl(_InIter __first, _Sent __last, _OutIter __result) {
while (__first != __last) {
*__result = std::move(*__first);
++__first;
++__result;
}
return std::make_pair(std::move(__first), std::move(__result));
}
template <class _InType,
class _OutType,
class = __enable_if_t<is_same<typename remove_const<_InType>::type, _OutType>::value
&& is_trivially_move_assignable<_OutType>::value> >
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX11
pair<_InType*, _OutType*> __move_impl(_InType* __first, _InType* __last, _OutType* __result) {
if (__libcpp_is_constant_evaluated()
// TODO: Remove this once GCC supports __builtin_memmove during constant evaluation
#ifndef _LIBCPP_COMPILER_GCC
&& !is_trivially_copyable<_InType>::value
#endif
)
return std::__move_impl<_InType*, _InType*, _OutType*>(__first, __last, __result);
const size_t __n = static_cast<size_t>(__last - __first);
::__builtin_memmove(__result, __first, __n * sizeof(_OutType));
return std::make_pair(__first + __n, __result + __n);
}
template <class>
struct __is_trivially_move_assignable_unwrapped_impl : false_type {};
template <class _Type>
struct __is_trivially_move_assignable_unwrapped_impl<_Type*> : is_trivially_move_assignable<_Type> {};
template <class _Iter>
struct __is_trivially_move_assignable_unwrapped
: __is_trivially_move_assignable_unwrapped_impl<decltype(std::__unwrap_iter<_Iter>(std::declval<_Iter>()))> {};
template <class _InIter,
class _OutIter,
__enable_if_t<is_same<typename remove_const<typename iterator_traits<_InIter>::value_type>::type,
typename iterator_traits<_OutIter>::value_type>::value
&& __is_cpp17_contiguous_iterator<_InIter>::value
&& __is_cpp17_contiguous_iterator<_OutIter>::value
&& is_trivially_move_assignable<__iter_value_type<_OutIter> >::value, int> = 0>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX14
pair<reverse_iterator<_InIter>, reverse_iterator<_OutIter> >
__move_impl(reverse_iterator<_InIter> __first,
reverse_iterator<_InIter> __last,
reverse_iterator<_OutIter> __result) {
auto __first_base = std::__unwrap_iter(__first.base());
auto __last_base = std::__unwrap_iter(__last.base());
auto __result_base = std::__unwrap_iter(__result.base());
auto __result_first = __result_base - (__first_base - __last_base);
std::__move_impl(__last_base, __first_base, __result_first);
return std::make_pair(__last, reverse_iterator<_OutIter>(std::__rewrap_iter(__result.base(), __result_first)));
}
template <class _InIter, class _Sent, class _OutIter>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX11
__enable_if_t<is_copy_constructible<_InIter>::value
&& is_copy_constructible<_Sent>::value
&& is_copy_constructible<_OutIter>::value, pair<_InIter, _OutIter> >
__move(_InIter __first, _Sent __last, _OutIter __result) {
auto __ret = std::__move_impl(std::__unwrap_iter(__first), std::__unwrap_iter(__last), std::__unwrap_iter(__result));
return std::make_pair(std::__rewrap_iter(__first, __ret.first), std::__rewrap_iter(__result, __ret.second));
}
template <class _InIter, class _Sent, class _OutIter>
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX11
__enable_if_t<!is_copy_constructible<_InIter>::value
|| !is_copy_constructible<_Sent>::value
|| !is_copy_constructible<_OutIter>::value, pair<_InIter, _OutIter> >
__move(_InIter __first, _Sent __last, _OutIter __result) {
return std::__move_impl(std::move(__first), std::move(__last), std::move(__result));
}
template <class _InputIterator, class _OutputIterator>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX14
_OutputIterator
__move(_InputIterator __first, _InputIterator __last, _OutputIterator __result)
{
return _VSTD::__move_constexpr(__first, __last, __result);
}
template <class _Tp, class _Up>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX14
typename enable_if
<
is_same<typename remove_const<_Tp>::type, _Up>::value &&
is_trivially_move_assignable<_Up>::value,
_Up*
>::type
__move(_Tp* __first, _Tp* __last, _Up* __result)
{
const size_t __n = static_cast<size_t>(__last - __first);
if (__n > 0)
_VSTD::memmove(__result, __first, __n * sizeof(_Up));
return __result + __n;
}
template <class _InputIterator, class _OutputIterator>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX17
_OutputIterator
move(_InputIterator __first, _InputIterator __last, _OutputIterator __result)
{
if (__libcpp_is_constant_evaluated()) {
return _VSTD::__move_constexpr(__first, __last, __result);
} else {
return _VSTD::__rewrap_iter(__result,
_VSTD::__move(_VSTD::__unwrap_iter(__first),
_VSTD::__unwrap_iter(__last),
_VSTD::__unwrap_iter(__result)));
}
inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
_OutputIterator move(_InputIterator __first, _InputIterator __last, _OutputIterator __result) {
return std::__move(__first, __last, __result).second;
}
_LIBCPP_END_NAMESPACE_STD

View File

@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___ALGORITHM_RANGES_MOVE_H
#define _LIBCPP___ALGORITHM_RANGES_MOVE_H
#include <__algorithm/in_out_result.h>
#include <__algorithm/move.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/iter_move.h>
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/dangling.h>
#include <__utility/move.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
_LIBCPP_BEGIN_NAMESPACE_STD
namespace ranges {
template <class _InIter, class _OutIter>
using move_result = in_out_result<_InIter, _OutIter>;
namespace __move {
struct __fn {
template <class _InIter, class _Sent, class _OutIter>
requires __iter_move::__move_deref<_InIter> // check that we are allowed to std::move() the value
_LIBCPP_HIDE_FROM_ABI constexpr static
move_result<_InIter, _OutIter> __move_impl(_InIter __first, _Sent __last, _OutIter __result) {
auto __ret = std::__move(std::move(__first), std::move(__last), std::move(__result));
return {std::move(__ret.first), std::move(__ret.second)};
}
template <class _InIter, class _Sent, class _OutIter>
_LIBCPP_HIDE_FROM_ABI constexpr static
move_result<_InIter, _OutIter> __move_impl(_InIter __first, _Sent __last, _OutIter __result) {
while (__first != __last) {
*__result = ranges::iter_move(__first);
++__first;
++__result;
}
return {std::move(__first), std::move(__result)};
}
template <input_iterator _InIter, sentinel_for<_InIter> _Sent, weakly_incrementable _OutIter>
requires indirectly_movable<_InIter, _OutIter>
_LIBCPP_HIDE_FROM_ABI constexpr
move_result<_InIter, _OutIter> operator()(_InIter __first, _Sent __last, _OutIter __result) const {
return __move_impl(std::move(__first), std::move(__last), std::move(__result));
}
template <input_range _Range, weakly_incrementable _OutIter>
requires indirectly_movable<iterator_t<_Range>, _OutIter>
_LIBCPP_HIDE_FROM_ABI constexpr
move_result<borrowed_iterator_t<_Range>, _OutIter> operator()(_Range&& __range, _OutIter __result) const {
return __move_impl(ranges::begin(__range), ranges::end(__range), std::move(__result));
}
};
} // namespace __move
inline namespace __cpo {
inline constexpr auto move = __move::__fn{};
} // namespace __cpo
} // namespace ranges
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
#endif // _LIBCPP___ALGORITHM_RANGES_MOVE_H

View File

@ -0,0 +1,75 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef _LIBCPP___ALGORITHM_RANGES_MOVE_BACKWARD_H
#define _LIBCPP___ALGORITHM_RANGES_MOVE_BACKWARD_H
#include <__algorithm/in_out_result.h>
#include <__algorithm/ranges_move.h>
#include <__config>
#include <__iterator/concepts.h>
#include <__iterator/iter_move.h>
#include <__iterator/next.h>
#include <__iterator/reverse_iterator.h>
#include <__ranges/access.h>
#include <__ranges/concepts.h>
#include <__ranges/dangling.h>
#include <__utility/move.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
#endif
#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
_LIBCPP_BEGIN_NAMESPACE_STD
namespace ranges {
template <class _InIter, class _OutIter>
using move_backward_result = in_out_result<_InIter, _OutIter>;
namespace __move_backward {
struct __fn {
template <class _InIter, class _Sent, class _OutIter>
_LIBCPP_HIDE_FROM_ABI constexpr static
move_backward_result<_InIter, _OutIter> __move_backward_impl(_InIter __first, _Sent __last, _OutIter __result) {
auto __ret = ranges::move(std::make_reverse_iterator(ranges::next(__first, __last)),
std::make_reverse_iterator(__first),
std::make_reverse_iterator(__result));
return {std::move(__ret.in.base()), std::move(__ret.out.base())};
}
template <bidirectional_iterator _InIter, sentinel_for<_InIter> _Sent, bidirectional_iterator _OutIter>
requires indirectly_movable<_InIter, _OutIter>
_LIBCPP_HIDE_FROM_ABI constexpr
move_backward_result<_InIter, _OutIter> operator()(_InIter __first, _Sent __last, _OutIter __result) const {
return __move_backward_impl(std::move(__first), std::move(__last), std::move(__result));
}
template <bidirectional_range _Range, bidirectional_iterator _Iter>
requires indirectly_movable<iterator_t<_Range>, _Iter>
_LIBCPP_HIDE_FROM_ABI constexpr
move_backward_result<borrowed_iterator_t<_Range>, _Iter> operator()(_Range&& __range, _Iter __result) const {
return __move_backward_impl(ranges::begin(__range), ranges::end(__range), std::move(__result));
}
};
} // namespace __move_backward
inline namespace __cpo {
inline constexpr auto move_backward = __move_backward::__fn{};
} // namespace __cpo
} // namespace ranges
_LIBCPP_END_NAMESPACE_STD
#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
#endif // _LIBCPP___ALGORITHM_RANGES_MOVE_BACKWARD_H

View File

@ -450,6 +450,27 @@ namespace ranges {
ranges::lexicographical_compare(R1&& r1, R2&& r2, Comp comp = {},
Proj1 proj1 = {}, Proj2 proj2 = {}); // since C++20
template<bidirectional_iterator I1, sentinel_for<I1> S1, bidirectional_iterator I2>
requires indirectly_movable<I1, I2>
constexpr ranges::move_backward_result<I1, I2>
ranges::move_backward(I1 first, S1 last, I2 result); // since C++20
template<bidirectional_range R, bidirectional_iterator I>
requires indirectly_movable<iterator_t<R>, I>
constexpr ranges::move_backward_result<borrowed_iterator_t<R>, I>
ranges::move_backward(R&& r, I result); // since C++20
template<input_iterator I, sentinel_for<I> S, weakly_incrementable O>
requires indirectly_movable<I, O>
constexpr ranges::move_result<I, O>
ranges::move(I first, S last, O result); // since C++20
template<input_range R, weakly_incrementable O>
requires indirectly_movable<iterator_t<R>, O>
constexpr ranges::move_result<borrowed_iterator_t<R>, O>
ranges::move(R&& r, O result); // since C++20
}
constexpr bool // constexpr in C++20
@ -1195,6 +1216,8 @@ template <class BidirectionalIterator, class Compare>
#include <__algorithm/ranges_minmax.h>
#include <__algorithm/ranges_minmax_element.h>
#include <__algorithm/ranges_mismatch.h>
#include <__algorithm/ranges_move.h>
#include <__algorithm/ranges_move_backward.h>
#include <__algorithm/ranges_none_of.h>
#include <__algorithm/ranges_replace.h>
#include <__algorithm/ranges_replace_if.h>

View File

@ -338,6 +338,8 @@ module std [system] {
module ranges_minmax { private header "__algorithm/ranges_minmax.h" }
module ranges_minmax_element { private header "__algorithm/ranges_minmax_element.h" }
module ranges_mismatch { private header "__algorithm/ranges_mismatch.h" }
module ranges_move { private header "__algorithm/ranges_move.h" }
module ranges_move_backward { private header "__algorithm/ranges_move_backward.h" }
module ranges_none_of { private header "__algorithm/ranges_none_of.h" }
module ranges_replace { private header "__algorithm/ranges_replace.h" }
module ranges_replace_if { private header "__algorithm/ranges_replace_if.h" }

View File

@ -136,6 +136,8 @@ END-SCRIPT
#include <__algorithm/ranges_minmax.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_minmax.h'}}
#include <__algorithm/ranges_minmax_element.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_minmax_element.h'}}
#include <__algorithm/ranges_mismatch.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_mismatch.h'}}
#include <__algorithm/ranges_move.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_move.h'}}
#include <__algorithm/ranges_move_backward.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_move_backward.h'}}
#include <__algorithm/ranges_none_of.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_none_of.h'}}
#include <__algorithm/ranges_replace.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_replace.h'}}
#include <__algorithm/ranges_replace_if.h> // expected-error@*:* {{use of private header from outside its module: '__algorithm/ranges_replace_if.h'}}

View File

@ -0,0 +1,259 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// <algorithm>
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// template<input_iterator I, sentinel_for<I> S, weakly_incrementable O>
// requires indirectly_movable<I, O>
// constexpr ranges::move_result<I, O>
// ranges::move(I first, S last, O result);
// template<input_range R, weakly_incrementable O>
// requires indirectly_movable<iterator_t<R>, O>
// constexpr ranges::move_result<borrowed_iterator_t<R>, O>
// ranges::move(R&& r, O result);
#include <algorithm>
#include <array>
#include <cassert>
#include <ranges>
#include "almost_satisfies_types.h"
#include "MoveOnly.h"
#include "test_iterators.h"
template <class In, class Out = In, class Sent = sentinel_wrapper<In>>
concept HasMoveIt = requires(In in, Sent sent, Out out) { std::ranges::move(in, sent, out); };
static_assert(HasMoveIt<int*>);
static_assert(!HasMoveIt<InputIteratorNotDerivedFrom>);
static_assert(!HasMoveIt<InputIteratorNotIndirectlyReadable>);
static_assert(!HasMoveIt<InputIteratorNotInputOrOutputIterator>);
static_assert(!HasMoveIt<int*, WeaklyIncrementableNotMovable>);
struct NotIndirectlyMovable {};
static_assert(!HasMoveIt<int*, NotIndirectlyMovable*>);
static_assert(!HasMoveIt<int*, int*, SentinelForNotSemiregular>);
static_assert(!HasMoveIt<int*, int*, SentinelForNotWeaklyEqualityComparableWith>);
template <class Range, class Out>
concept HasMoveR = requires(Range range, Out out) { std::ranges::move(range, out); };
static_assert(HasMoveR<std::array<int, 10>, int*>);
static_assert(!HasMoveR<InputRangeNotDerivedFrom, int*>);
static_assert(!HasMoveR<InputRangeNotIndirectlyReadable, int*>);
static_assert(!HasMoveR<InputRangeNotInputOrOutputIterator, int*>);
static_assert(!HasMoveR<WeaklyIncrementableNotMovable, int*>);
static_assert(!HasMoveR<UncheckedRange<NotIndirectlyMovable*>, int*>);
static_assert(!HasMoveR<InputRangeNotSentinelSemiregular, int*>);
static_assert(!HasMoveR<InputRangeNotSentinelEqualityComparableWith, int*>);
static_assert(!HasMoveR<UncheckedRange<int*>, WeaklyIncrementableNotMovable>);
static_assert(std::is_same_v<std::ranges::move_result<int, long>, std::ranges::in_out_result<int, long>>);
template <class In, class Out, class Sent, int N>
constexpr void test(std::array<int, N> in) {
{
std::array<int, N> out;
std::same_as<std::ranges::in_out_result<In, Out>> decltype(auto) ret =
std::ranges::move(In(in.data()), Sent(In(in.data() + in.size())), Out(out.data()));
assert(in == out);
assert(base(ret.in) == in.data() + in.size());
assert(base(ret.out) == out.data() + out.size());
}
{
std::array<int, N> out;
auto range = std::ranges::subrange(In(in.data()), Sent(In(in.data() + in.size())));
std::same_as<std::ranges::in_out_result<In, Out>> decltype(auto) ret =
std::ranges::move(range, Out(out.data()));
assert(in == out);
assert(base(ret.in) == in.data() + in.size());
assert(base(ret.out) == out.data() + out.size());
}
}
template <class In, class Out, class Sent = In>
constexpr void test_iterators() {
// simple test
test<In, Out, Sent, 4>({1, 2, 3, 4});
// check that an empty range works
test<In, Out, Sent, 0>({});
}
template <class Out>
constexpr void test_in_iterators() {
test_iterators<cpp20_input_iterator<int*>, Out, sentinel_wrapper<cpp20_input_iterator<int*>>>();
test_iterators<forward_iterator<int*>, Out>();
test_iterators<bidirectional_iterator<int*>, Out>();
test_iterators<random_access_iterator<int*>, Out>();
test_iterators<contiguous_iterator<int*>, Out>();
}
struct IteratorWithMoveIter {
using value_type = int;
using difference_type = int;
explicit IteratorWithMoveIter() = default;
int* ptr;
constexpr IteratorWithMoveIter(int* ptr_) : ptr(ptr_) {}
constexpr int& operator*() const; // iterator with iter_move should not be dereferenced
constexpr IteratorWithMoveIter& operator++() { ++ptr; return *this; }
constexpr IteratorWithMoveIter operator++(int) { auto ret = *this; ++*this; return ret; }
friend constexpr int iter_move(const IteratorWithMoveIter&) { return 42; }
constexpr bool operator==(const IteratorWithMoveIter& other) const = default;
};
constexpr bool test() {
test_in_iterators<cpp17_output_iterator<int*>>();
test_in_iterators<cpp20_output_iterator<int*>>();
test_in_iterators<cpp17_input_iterator<int*>>();
test_in_iterators<cpp20_input_iterator<int*>>();
test_in_iterators<forward_iterator<int*>>();
test_in_iterators<bidirectional_iterator<int*>>();
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
{ // check that a move-only type works
{
MoveOnly a[] = {1, 2, 3};
MoveOnly b[3];
std::ranges::move(a, std::begin(b));
assert(b[0].get() == 1);
assert(b[1].get() == 2);
assert(b[2].get() == 3);
}
{
MoveOnly a[] = {1, 2, 3};
MoveOnly b[3];
std::ranges::move(std::begin(a), std::end(a), std::begin(b));
assert(b[0].get() == 1);
assert(b[1].get() == 2);
assert(b[2].get() == 3);
}
}
{ // check that ranges::dangling is returned
std::array<int, 4> out;
std::same_as<std::ranges::in_out_result<std::ranges::dangling, int*>> decltype(auto) ret =
std::ranges::move(std::array {1, 2, 3, 4}, out.data());
assert(ret.out == out.data() + 4);
assert((out == std::array{1, 2, 3, 4}));
}
{ // check that an iterator is returned with a borrowing range
std::array in {1, 2, 3, 4};
std::array<int, 4> out;
std::same_as<std::ranges::in_out_result<int*, int*>> decltype(auto) ret =
std::ranges::move(std::views::all(in), out.data());
assert(ret.in == in.data() + 4);
assert(ret.out == out.data() + 4);
assert(in == out);
}
{ // check that every element is moved exactly once
struct MoveOnce {
bool moved = false;
constexpr MoveOnce() = default;
constexpr MoveOnce(const MoveOnce& other) = delete;
constexpr MoveOnce& operator=(MoveOnce&& other) {
assert(!other.moved);
moved = true;
return *this;
}
};
{
std::array<MoveOnce, 4> in {};
std::array<MoveOnce, 4> out {};
auto ret = std::ranges::move(in.begin(), in.end(), out.begin());
assert(ret.in == in.end());
assert(ret.out == out.end());
assert(std::all_of(out.begin(), out.end(), [](const auto& e) { return e.moved; }));
}
{
std::array<MoveOnce, 4> in {};
std::array<MoveOnce, 4> out {};
auto ret = std::ranges::move(in, out.begin());
assert(ret.in == in.end());
assert(ret.out == out.end());
assert(std::all_of(out.begin(), out.end(), [](const auto& e) { return e.moved; }));
}
}
{ // check that the range is moved forwards
struct OnlyForwardsMovable {
OnlyForwardsMovable* next = nullptr;
bool canMove = false;
OnlyForwardsMovable() = default;
constexpr OnlyForwardsMovable& operator=(OnlyForwardsMovable&&) {
assert(canMove);
if (next != nullptr)
next->canMove = true;
return *this;
}
};
{
std::array<OnlyForwardsMovable, 3> in {};
std::array<OnlyForwardsMovable, 3> out {};
out[0].next = &out[1];
out[1].next = &out[2];
out[0].canMove = true;
auto ret = std::ranges::move(in.begin(), in.end(), out.begin());
assert(ret.in == in.end());
assert(ret.out == out.end());
assert(out[0].canMove);
assert(out[1].canMove);
assert(out[2].canMove);
}
{
std::array<OnlyForwardsMovable, 3> in {};
std::array<OnlyForwardsMovable, 3> out {};
out[0].next = &out[1];
out[1].next = &out[2];
out[0].canMove = true;
auto ret = std::ranges::move(in, out.begin());
assert(ret.in == in.end());
assert(ret.out == out.end());
assert(out[0].canMove);
assert(out[1].canMove);
assert(out[2].canMove);
}
}
{ // check that iter_move is used properly
{
int a[] = {1, 2, 3, 4};
std::array<int, 4> b;
auto ret = std::ranges::move(IteratorWithMoveIter(a), IteratorWithMoveIter(a + 4), b.data());
assert(ret.in == a + 4);
assert(ret.out == b.data() + 4);
assert((b == std::array {42, 42, 42, 42}));
}
{
int a[] = {1, 2, 3, 4};
std::array<int, 4> b;
auto range = std::ranges::subrange(IteratorWithMoveIter(a), IteratorWithMoveIter(a + 4));
auto ret = std::ranges::move(range, b.data());
assert(ret.in == a + 4);
assert(ret.out == b.data() + 4);
assert((b == std::array {42, 42, 42, 42}));
}
}
return true;
}
int main(int, char**) {
test();
static_assert(test());
return 0;
}

View File

@ -0,0 +1,256 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// <algorithm>
// UNSUPPORTED: c++03, c++11, c++14, c++17
// UNSUPPORTED: libcpp-has-no-incomplete-ranges
// template<bidirectional_iterator I1, sentinel_for<I1> S1, bidirectional_iterator I2>
// requires indirectly_movable<I1, I2>
// constexpr ranges::move_backward_result<I1, I2>
// ranges::move_backward(I1 first, S1 last, I2 result);
// template<bidirectional_range R, bidirectional_iterator I>
// requires indirectly_movable<iterator_t<R>, I>
// constexpr ranges::move_backward_result<borrowed_iterator_t<R>, I>
// ranges::move_backward(R&& r, I result);
#include <algorithm>
#include <array>
#include <cassert>
#include <ranges>
#include "almost_satisfies_types.h"
#include "MoveOnly.h"
#include "test_iterators.h"
template <class In, class Out = In, class Sent = sentinel_wrapper<In>>
concept HasMoveBackwardIt = requires(In in, Sent sent, Out out) { std::ranges::move_backward(in, sent, out); };
static_assert(HasMoveBackwardIt<int*>);
static_assert(!HasMoveBackwardIt<InputIteratorNotDerivedFrom>);
static_assert(!HasMoveBackwardIt<InputIteratorNotIndirectlyReadable>);
static_assert(!HasMoveBackwardIt<InputIteratorNotInputOrOutputIterator>);
static_assert(!HasMoveBackwardIt<int*, WeaklyIncrementableNotMovable>);
struct NotIndirectlyCopyable {};
static_assert(!HasMoveBackwardIt<int*, NotIndirectlyCopyable*>);
static_assert(!HasMoveBackwardIt<int*, int*, SentinelForNotSemiregular>);
static_assert(!HasMoveBackwardIt<int*, int*, SentinelForNotWeaklyEqualityComparableWith>);
template <class Range, class Out>
concept HasMoveBackwardR = requires(Range range, Out out) { std::ranges::move_backward(range, out); };
static_assert(HasMoveBackwardR<std::array<int, 10>, int*>);
static_assert(!HasMoveBackwardR<InputRangeNotDerivedFrom, int*>);
static_assert(!HasMoveBackwardR<InputRangeNotIndirectlyReadable, int*>);
static_assert(!HasMoveBackwardR<InputRangeNotInputOrOutputIterator, int*>);
static_assert(!HasMoveBackwardR<WeaklyIncrementableNotMovable, int*>);
static_assert(!HasMoveBackwardR<UncheckedRange<NotIndirectlyCopyable*>, int*>);
static_assert(!HasMoveBackwardR<InputRangeNotSentinelSemiregular, int*>);
static_assert(!HasMoveBackwardR<InputRangeNotSentinelEqualityComparableWith, int*>);
static_assert(!HasMoveBackwardR<UncheckedRange<int*>, WeaklyIncrementableNotMovable>);
static_assert(std::is_same_v<std::ranges::copy_result<int, long>, std::ranges::in_out_result<int, long>>);
template <class In, class Out, class Sent, int N>
constexpr void test(std::array<int, N> in) {
{
std::array<int, N> out;
std::same_as<std::ranges::in_out_result<In, Out>> decltype(auto) ret =
std::ranges::move_backward(In(in.data()), Sent(In(in.data() + in.size())), Out(out.data() + out.size()));
assert(in == out);
assert(base(ret.in) == in.data());
assert(base(ret.out) == out.data());
}
{
std::array<int, N> out;
auto range = std::ranges::subrange(In(in.data()), Sent(In(in.data() + in.size())));
std::same_as<std::ranges::in_out_result<In, Out>> decltype(auto) ret =
std::ranges::move_backward(range, Out(out.data() + out.size()));
assert(in == out);
assert(base(ret.in) == in.data());
assert(base(ret.out) == out.data());
}
}
template <class In, class Out, class Sent = In>
constexpr void test_iterators() {
// simple test
test<In, Out, Sent, 4>({1, 2, 3, 4});
// check that an empty range works
test<In, Out, Sent, 0>({});
}
template <class Out>
constexpr void test_in_iterators() {
test_iterators<bidirectional_iterator<int*>, Out, sentinel_wrapper<bidirectional_iterator<int*>>>();
test_iterators<bidirectional_iterator<int*>, Out>();
test_iterators<random_access_iterator<int*>, Out>();
test_iterators<contiguous_iterator<int*>, Out>();
}
struct IteratorWithMoveIter {
using value_type = int;
using difference_type = int;
explicit IteratorWithMoveIter() = default;
int* ptr;
constexpr IteratorWithMoveIter(int* ptr_) : ptr(ptr_) {}
constexpr int& operator*() const; // iterator with iter_move should not be dereferenced
constexpr IteratorWithMoveIter& operator++() { ++ptr; return *this; }
constexpr IteratorWithMoveIter operator++(int) { auto ret = *this; ++*this; return ret; }
constexpr IteratorWithMoveIter& operator--() { --ptr; return *this; }
constexpr IteratorWithMoveIter operator--(int) { auto ret = *this; --*this; return ret; }
friend constexpr int iter_move(const IteratorWithMoveIter&) { return 42; }
constexpr bool operator==(const IteratorWithMoveIter& other) const = default;
};
constexpr bool test() {
test_in_iterators<bidirectional_iterator<int*>>();
test_in_iterators<random_access_iterator<int*>>();
test_in_iterators<contiguous_iterator<int*>>();
{ // check that a move-only type works
{
MoveOnly a[] = {1, 2, 3};
MoveOnly b[3];
std::ranges::move_backward(a, std::end(b));
assert(b[0].get() == 1);
assert(b[1].get() == 2);
assert(b[2].get() == 3);
}
{
MoveOnly a[] = {1, 2, 3};
MoveOnly b[3];
std::ranges::move_backward(std::begin(a), std::end(a), std::end(b));
assert(b[0].get() == 1);
assert(b[1].get() == 2);
assert(b[2].get() == 3);
}
}
{ // check that ranges::dangling is returned
std::array<int, 4> out;
std::same_as<std::ranges::in_out_result<std::ranges::dangling, int*>> auto ret =
std::ranges::move_backward(std::array {1, 2, 3, 4}, out.data() + out.size());
assert(ret.out == out.data());
assert((out == std::array{1, 2, 3, 4}));
}
{ // check that an iterator is returned with a borrowing range
std::array in {1, 2, 3, 4};
std::array<int, 4> out;
std::same_as<std::ranges::in_out_result<int*, int*>> auto ret =
std::ranges::move_backward(std::views::all(in), out.data() + out.size());
assert(ret.in == in.data());
assert(ret.out == out.data());
assert(in == out);
}
{ // check that every element is moved exactly once
struct MoveOnce {
bool moved = false;
constexpr MoveOnce() = default;
constexpr MoveOnce(const MoveOnce& other) = delete;
constexpr MoveOnce& operator=(const MoveOnce& other) {
assert(!other.moved);
moved = true;
return *this;
}
};
{
std::array<MoveOnce, 4> in {};
std::array<MoveOnce, 4> out {};
auto ret = std::ranges::move_backward(in.begin(), in.end(), out.end());
assert(ret.in == in.begin());
assert(ret.out == out.begin());
assert(std::all_of(out.begin(), out.end(), [](const auto& e) { return e.moved; }));
}
{
std::array<MoveOnce, 4> in {};
std::array<MoveOnce, 4> out {};
auto ret = std::ranges::move_backward(in, out.end());
assert(ret.in == in.begin());
assert(ret.out == out.begin());
assert(std::all_of(out.begin(), out.end(), [](const auto& e) { return e.moved; }));
}
}
{ // check that the range is moved backwards
struct OnlyBackwardsMovable {
OnlyBackwardsMovable* next = nullptr;
bool canMove = false;
OnlyBackwardsMovable() = default;
constexpr OnlyBackwardsMovable& operator=(const OnlyBackwardsMovable&) {
assert(canMove);
if (next != nullptr)
next->canMove = true;
return *this;
}
};
{
std::array<OnlyBackwardsMovable, 3> in {};
std::array<OnlyBackwardsMovable, 3> out {};
out[1].next = &out[0];
out[2].next = &out[1];
out[2].canMove = true;
auto ret = std::ranges::move_backward(in, out.end());
assert(ret.in == in.begin());
assert(ret.out == out.begin());
assert(out[0].canMove);
assert(out[1].canMove);
assert(out[2].canMove);
}
{
std::array<OnlyBackwardsMovable, 3> in {};
std::array<OnlyBackwardsMovable, 3> out {};
out[1].next = &out[0];
out[2].next = &out[1];
out[2].canMove = true;
auto ret = std::ranges::move_backward(in.begin(), in.end(), out.end());
assert(ret.in == in.begin());
assert(ret.out == out.begin());
assert(out[0].canMove);
assert(out[1].canMove);
assert(out[2].canMove);
}
}
{ // check that iter_move is used properly
{
int a[] = {1, 2, 3, 4};
std::array<int, 4> b;
auto ret = std::ranges::move_backward(IteratorWithMoveIter(a), IteratorWithMoveIter(a + 4), b.data() + b.size());
assert(ret.in == a);
assert(ret.out == b.data());
assert((b == std::array {42, 42, 42, 42}));
}
{
int a[] = {1, 2, 3, 4};
std::array<int, 4> b;
auto range = std::ranges::subrange(IteratorWithMoveIter(a), IteratorWithMoveIter(a + 4));
auto ret = std::ranges::move_backward(range, b.data() + b.size());
assert(ret.in == a);
assert(ret.out == b.data());
assert((b == std::array {42, 42, 42, 42}));
}
}
return true;
}
int main(int, char**) {
test();
static_assert(test());
return 0;
}

View File

@ -104,8 +104,8 @@ static_assert(test(std::ranges::min_element, a));
static_assert(test(std::ranges::minmax, a));
static_assert(test(std::ranges::minmax_element, a));
static_assert(test(std::ranges::mismatch, a, a));
//static_assert(test(std::ranges::move, a, a));
//static_assert(test(std::ranges::move_backward, a, a));
static_assert(test(std::ranges::move, a, a));
static_assert(test(std::ranges::move_backward, a, a));
//static_assert(test(std::ranges::next_permutation, a));
static_assert(test(std::ranges::none_of, a, odd));
//static_assert(test(std::ranges::nth_element, a, a+5));