Skip to content

Commit e331c5e

Browse files
committed
[libc++] Fix {deque,vector}::append_range assuming too much about the types
1 parent bb4ed55 commit e331c5e

File tree

12 files changed

+139
-7
lines changed

12 files changed

+139
-7
lines changed

libcxx/docs/ReleaseNotes/22.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Improvements and New Features
7070
in chunks into a buffer.
7171
- Multiple internal types have been refactored to use ``[[no_unique_address]]``, resulting in faster compile times and
7272
reduced debug information.
73+
- The performance of ``deque::append_range`` has been improved by up to 3.4x
7374

7475
- The performance of ``std::find`` has been improved by up to 2x for integral types
7576
- The ``std::distance`` and ``std::ranges::distance`` algorithms have been optimized for segmented iterators (e.g.,

libcxx/include/__vector/vector.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
#include <__memory/temp_value.h>
4343
#include <__memory/uninitialized_algorithms.h>
4444
#include <__ranges/access.h>
45+
#include <__ranges/as_rvalue_view.h>
4546
#include <__ranges/concepts.h>
4647
#include <__ranges/container_compatible_range.h>
4748
#include <__ranges/from_range.h>
@@ -489,7 +490,21 @@ class vector {
489490
#if _LIBCPP_STD_VER >= 23
490491
template <_ContainerCompatibleRange<_Tp> _Range>
491492
_LIBCPP_HIDE_FROM_ABI constexpr void append_range(_Range&& __range) {
492-
insert_range(end(), std::forward<_Range>(__range));
493+
if constexpr (ranges::forward_range<_Range> || ranges::sized_range<_Range>) {
494+
auto __len = ranges::distance(__range);
495+
if (__len < __cap_ - __end_) {
496+
__construct_at_end(ranges::begin(__range), ranges::end(__range), __len);
497+
} else {
498+
__split_buffer<value_type, allocator_type&> __buffer(__recommend(size() + __len), size(), __alloc_);
499+
__buffer.__construct_at_end_with_size(ranges::begin(__range), __len);
500+
__swap_out_circular_buffer(__buffer);
501+
}
502+
} else {
503+
vector __buffer(__alloc_);
504+
for (auto&& __val : __range)
505+
__buffer.emplace_back(std::forward<decltype(__val)>(__val));
506+
append_range(ranges::as_rvalue_view(__buffer));
507+
}
493508
}
494509
#endif
495510

libcxx/include/deque

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -805,7 +805,11 @@ public:
805805

806806
template <_ContainerCompatibleRange<_Tp> _Range>
807807
_LIBCPP_HIDE_FROM_ABI void append_range(_Range&& __range) {
808-
insert_range(end(), std::forward<_Range>(__range));
808+
if constexpr (ranges::forward_range<_Range> || ranges::sized_range<_Range>) {
809+
__append_with_size(ranges::begin(__range), ranges::distance(__range));
810+
} else {
811+
__append_with_sentinel(ranges::begin(__range), ranges::end(__range));
812+
}
809813
}
810814
# endif
811815

@@ -1459,13 +1463,13 @@ _LIBCPP_HIDE_FROM_ABI void deque<_Tp, _Allocator>::__assign_with_size(_Iterator
14591463

14601464
auto __i = begin();
14611465
for (auto __count = size(); __count != 0; --__count) {
1462-
*__i++ = *__f++;
1466+
*__i++ = *__f;
1467+
++__f;
14631468
}
14641469

1465-
__append_with_size(__f, __added_size);
1466-
1470+
__append_with_size(std::move(__f), __added_size);
14671471
} else {
1468-
__erase_to_end(std::copy_n(__f, __n, begin()));
1472+
__erase_to_end(std::copy_n(std::move(__f), __n, begin()));
14691473
}
14701474
}
14711475

libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ void sequence_container_benchmarks(std::string container) {
309309
}
310310

311311
/////////////////////////
312-
// Variations of push_back
312+
// Appending elements
313313
/////////////////////////
314314
static constexpr bool has_push_back = requires(Container c, ValueType v) { c.push_back(v); };
315315
static constexpr bool has_capacity = requires(Container c) { c.capacity(); };
@@ -399,6 +399,53 @@ void sequence_container_benchmarks(std::string container) {
399399
st.ResumeTiming();
400400
}
401401
});
402+
403+
#if TEST_STD_VER >= 23
404+
for (auto gen : generators)
405+
bench("append_range() (into empty container)" + tostr(gen), [gen](auto& state) {
406+
auto const size = state.range(0);
407+
std::vector<ValueType> in;
408+
std::generate_n(std::back_inserter(in), size, gen);
409+
DoNotOptimizeData(in);
410+
411+
Container c;
412+
DoNotOptimizeData(c);
413+
for (auto _ : state) {
414+
c.append_range(in);
415+
DoNotOptimizeData(c);
416+
417+
state.PauseTiming();
418+
c.clear();
419+
state.ResumeTiming();
420+
}
421+
});
422+
#endif
423+
}
424+
425+
/////////////////////////
426+
// Prepending elements
427+
/////////////////////////
428+
static constexpr bool has_prepend_range = requires(Container c, std::vector<ValueType> v) { c.prepend_range(v); };
429+
430+
if constexpr (has_prepend_range) {
431+
for (auto gen : generators)
432+
bench("prepend_range() (into empty container)" + tostr(gen), [gen](auto& state) {
433+
auto const size = state.range(0);
434+
std::vector<ValueType> in;
435+
std::generate_n(std::back_inserter(in), size, gen);
436+
DoNotOptimizeData(in);
437+
438+
Container c;
439+
DoNotOptimizeData(c);
440+
for (auto _ : state) {
441+
c.prepend_range(in);
442+
DoNotOptimizeData(c);
443+
444+
state.PauseTiming();
445+
c.clear();
446+
state.ResumeTiming();
447+
}
448+
});
402449
}
403450

404451
/////////////////////////

libcxx/test/std/containers/insert_range_helpers.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ constexpr void for_all_iterators_and_allocators(Func f) {
9494
f.template operator()<Iter, Iter, safe_allocator<T>>();
9595
}
9696
});
97+
// This is added because otherwise there would be no input-only sized range, which has fewer guarantees than a
98+
// forward and sized range. We don't want to put it in the for_each above to avoid a combinatorial explosion.
99+
f.template operator()<cpp20_input_iterator<PtrT>, sized_sentinel<cpp20_input_iterator<PtrT>>, std::allocator<T>>();
97100
}
98101

99102
// Uses a shorter list of iterator types for use in `constexpr` mode for cases when running the full set in would take

libcxx/test/std/containers/sequences/deque/deque.modifiers/append_range.pass.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ int main(int, char**) {
3131
});
3232
});
3333
test_sequence_append_range_move_only<std::deque>();
34+
test_sequence_append_range_emplace_constructible<std::deque>();
3435

3536
test_append_range_exception_safety_throwing_copy<std::deque>();
3637
test_append_range_exception_safety_throwing_allocator<std::deque, int>();

libcxx/test/std/containers/sequences/deque/deque.modifiers/prepend_range.pass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ int main(int, char**) {
3131
});
3232
});
3333
test_sequence_prepend_range_move_only<std::deque>();
34+
// FIXME: This should work - see https://llvm.org/PR162605
35+
// test_sequence_prepend_range_emplace_constructible<std::deque>();
3436

3537
test_prepend_range_exception_safety_throwing_copy<std::deque>();
3638
test_prepend_range_exception_safety_throwing_allocator<std::deque, int>();

libcxx/test/std/containers/sequences/forwardlist/forwardlist.modifiers/prepend_range.pass.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ TEST_CONSTEXPR_CXX26 bool test() {
3030
});
3131
});
3232
test_sequence_prepend_range_move_only<std::forward_list>();
33+
test_sequence_prepend_range_emplace_constructible<std::forward_list>();
3334

3435
if (!TEST_IS_CONSTANT_EVALUATED) {
3536
test_prepend_range_exception_safety_throwing_copy<std::forward_list>();

libcxx/test/std/containers/sequences/insert_range_sequence_containers.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,61 @@ constexpr void test_sequence_assign_range_move_only() {
643643
c.assign_range(in);
644644
}
645645

646+
struct InPlaceOnly {
647+
constexpr InPlaceOnly() {}
648+
InPlaceOnly(const InPlaceOnly&) = delete;
649+
InPlaceOnly(InPlaceOnly&&) = delete;
650+
InPlaceOnly& operator=(const InPlaceOnly&) = delete;
651+
InPlaceOnly& operator=(InPlaceOnly&&) = delete;
652+
};
653+
654+
struct EmplaceConstructible {
655+
EmplaceConstructible(const EmplaceConstructible&) = delete;
656+
EmplaceConstructible& operator=(const EmplaceConstructible&) = delete;
657+
EmplaceConstructible& operator=(EmplaceConstructible&&) = delete;
658+
EmplaceConstructible(EmplaceConstructible&&) = delete;
659+
constexpr EmplaceConstructible(const InPlaceOnly&) {}
660+
};
661+
662+
template <template <class...> class Container>
663+
constexpr void test_sequence_append_range_emplace_constructible() {
664+
InPlaceOnly input[5];
665+
types::for_each(types::cpp20_input_iterator_list<InPlaceOnly*>{}, [&]<class Iter> {
666+
std::ranges::subrange in(Iter(input), sentinel_wrapper<Iter>(Iter(input + 5)));
667+
Container<EmplaceConstructible> c;
668+
c.append_range(in);
669+
});
670+
}
671+
672+
template <template <class...> class Container>
673+
constexpr void test_sequence_prepend_range_emplace_constructible() {
674+
InPlaceOnly input[5];
675+
types::for_each(types::cpp20_input_iterator_list<InPlaceOnly*>{}, [&]<class Iter> {
676+
std::ranges::subrange in(Iter(input), sentinel_wrapper<Iter>(Iter(input + 5)));
677+
Container<EmplaceConstructible> c;
678+
c.prepend_range(in);
679+
});
680+
}
681+
682+
// vector has a special requirement that the type also has to be Cpp17MoveInsertable
683+
struct EmplaceConstructibleAndMoveInsertable {
684+
EmplaceConstructibleAndMoveInsertable(const EmplaceConstructibleAndMoveInsertable&) = delete;
685+
EmplaceConstructibleAndMoveInsertable& operator=(const EmplaceConstructibleAndMoveInsertable&) = delete;
686+
EmplaceConstructibleAndMoveInsertable& operator=(EmplaceConstructibleAndMoveInsertable&&) = delete;
687+
constexpr EmplaceConstructibleAndMoveInsertable(EmplaceConstructibleAndMoveInsertable&&) {}
688+
constexpr EmplaceConstructibleAndMoveInsertable(const InPlaceOnly&) {}
689+
};
690+
691+
template <template <class...> class Container>
692+
constexpr void test_sequence_append_range_emplace_constructible_and_move_insertable() {
693+
InPlaceOnly input[5];
694+
types::for_each(types::cpp20_input_iterator_list<InPlaceOnly*>{}, [&]<class Iter> {
695+
std::ranges::subrange in(Iter(input), sentinel_wrapper<Iter>(Iter(input + 5)));
696+
Container<EmplaceConstructibleAndMoveInsertable> c;
697+
c.append_range(in);
698+
});
699+
}
700+
646701
// Exception safety.
647702

648703
template <template <class...> class Container>

libcxx/test/std/containers/sequences/list/list.modifiers/append_range.pass.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ TEST_CONSTEXPR_CXX26 bool test() {
3131
});
3232
});
3333
test_sequence_append_range_move_only<std::list>();
34+
test_sequence_append_range_emplace_constructible<std::list>();
3435

3536
if (!TEST_IS_CONSTANT_EVALUATED) {
3637
test_append_range_exception_safety_throwing_copy<std::list>();

0 commit comments

Comments
 (0)