Add an overload of absl::c_move to move between containers. This change introduces a new overload for absl::c_move that takes two ranges, allowing elements to be moved from a source container to a destination container. The destination container must be fixed size so we can perform bounds checking. PiperOrigin-RevId: 919189227 Change-Id: Ica35c5d8bd59ebe16564f2b2f490770899ad16f8
diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index 965c545..c0934f7 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h
@@ -686,10 +686,36 @@ // Container-based version of the <algorithm> `std::move()` function to move // a container's elements into an iterator. template <typename C, typename OutputIterator> -ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_move(C&& src, - OutputIterator dest) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + std::enable_if_t<container_algorithm_internal::IsIterator< + absl::remove_cvref_t<OutputIterator>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<C>>::value, + std::decay_t<OutputIterator>> + c_move(C&& src, OutputIterator&& dest) { return std::move(container_algorithm_internal::c_begin(src), - container_algorithm_internal::c_end(src), dest); + container_algorithm_internal::c_end(src), + std::forward<OutputIterator>(dest)); +} + +// Moves elements from `src` to `dest`. `absl::c_move(src, dest)` is +// equivalent to `std::move(std::begin(src), std::end(src), std::begin(dest))`. +// +// The `dest` container must be large enough to hold all elements of `src`; +// this function does not resize `dest`. +template <typename C, typename OutputRange> +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + std::enable_if_t<container_algorithm_internal::HasBeginEnd< + std::add_lvalue_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<OutputRange>>::value && + !container_algorithm_internal::IsMultidimensionalArray< + std::remove_reference_t<C>>::value, + void> + c_move(C&& src, OutputRange&& dest) { + container_algorithm_internal::AssertCopySize(src, dest); + absl::c_move(std::forward<C>(src), container_algorithm_internal::c_begin( + std::forward<OutputRange>(dest))); } // c_move_backward()
diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index fcb8492..6bacb1e 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc
@@ -957,6 +957,95 @@ Pointee(2), Pointee(3))); } +TEST(MutatingTest, MoveToContainer) { + std::vector<std::unique_ptr<int>> input; + input.push_back(std::make_unique<int>(1)); + input.push_back(std::make_unique<int>(2)); + input.push_back(std::make_unique<int>(3)); + + std::vector<std::unique_ptr<int>> actual(5); + absl::c_move(input, actual); + + EXPECT_EQ(input[0], nullptr); + EXPECT_EQ(input[1], nullptr); + EXPECT_EQ(input[2], nullptr); + + ASSERT_NE(actual[0], nullptr); + EXPECT_EQ(*actual[0], 1); + ASSERT_NE(actual[1], nullptr); + EXPECT_EQ(*actual[1], 2); + ASSERT_NE(actual[2], nullptr); + EXPECT_EQ(*actual[2], 3); + EXPECT_EQ(actual[3], nullptr); +} + +TEST(MutatingTest, MoveToDifferentContainerType) { + std::list<std::unique_ptr<int>> input; + input.push_back(std::make_unique<int>(1)); + input.push_back(std::make_unique<int>(2)); + + std::array<std::unique_ptr<int>, 3> actual; + absl::c_move(input, actual); + + EXPECT_EQ(input.front(), nullptr); + ASSERT_NE(actual[0], nullptr); + EXPECT_EQ(*actual[0], 1); +} + +TEST(MutatingTest, MoveToCArray) { + std::vector<std::unique_ptr<int>> input; + input.push_back(std::make_unique<int>(1)); + input.push_back(std::make_unique<int>(2)); + + std::unique_ptr<int> actual[3]; + absl::c_move(input, actual); + + EXPECT_EQ(input.front(), nullptr); + ASSERT_NE(actual[0], nullptr); + EXPECT_EQ(*actual[0], 1); +} + +TEST(MutatingTest, MoveFromCArray) { + std::unique_ptr<int> input[2]; + input[0] = std::make_unique<int>(1); + input[1] = std::make_unique<int>(2); + + std::vector<std::unique_ptr<int>> actual(3); + absl::c_move(input, actual); + + EXPECT_EQ(input[0], nullptr); + ASSERT_NE(actual[0], nullptr); + EXPECT_EQ(*actual[0], 1); +} + +#if GTEST_HAS_DEATH_TEST + +TEST(MutatingTest, MoveToCArrayInvalidSize) { + std::vector<int> input = {1, 2, 3}; + int actual[2] = {0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_move(input, actual), ""); + } +} + +TEST(MutatingTest, MoveToContainerInvalidSize) { + std::list<int> input = {1, 2, 3, 4, 5}; + std::list<int> actual = {0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_move(input, actual), ""); + } +} + +TEST(MutatingTest, MoveToForwardListInvalidSize) { + std::forward_list<int> input = {1, 2, 3, 4, 5}; + std::forward_list<int> actual = {0, 0, 0}; + if (IsHardened()) { + EXPECT_DEATH(absl::c_move(input, actual), ""); + } +} + +#endif // GTEST_HAS_DEATH_TEST + TEST(MutatingTest, SwapRanges) { std::vector<int> odds = {2, 4, 6}; std::vector<int> evens = {1, 3, 5}; @@ -2459,6 +2548,14 @@ std::declval<Container>(), std::declval<ptrdiff_t>(), std::declval<Output>()))>> : std::true_type {}; +template <typename Container, typename Output, typename = void> +struct CanMove : std::false_type {}; +template <typename Container, typename Output> +struct CanMove<Container, Output, + absl::void_t<decltype(absl::c_move(std::declval<Container>(), + std::declval<Output>()))>> + : std::true_type {}; + TEST(CanCopyTest, CopyToMultiDimArray) { static_assert(CanCopy<std::vector<int>, int (&)[10]>::value); static_assert(!CanCopy<std::vector<int>, int (&)[2][2]>::value); @@ -2491,4 +2588,22 @@ static_assert(!CanCopyN<Vec, AmbiguousType>::value, "Ambiguous types should not compile!"); } + +TEST(CanMoveTest, MoveToMultiDimArray) { + static_assert(CanMove<std::vector<int>, int (&)[10]>::value); + static_assert(!CanMove<std::vector<int>, int (&)[2][2]>::value); + + static_assert(CanMove<int[10], int (&)[10]>::value); + static_assert(!CanMove<int[10], int (&)[2][2]>::value); + static_assert(!CanMove<int[2][2], int (&)[4]>::value); + static_assert(!CanMove<int[2][2], int (&)[2][2]>::value); +} + +TEST(CanMoveTest, AmbiguousTypeFailsToCompile) { + using Vec = std::vector<int>; + // Because AmbiguousType is both an iterator and a container, + // the compiler should fail to resolve the c_move overload. + static_assert(!CanMove<Vec, AmbiguousType>::value, + "Ambiguous types should not compile!"); +} } // namespace