Skip to content

Commit 38f2962

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

File tree

12 files changed

+138
-3
lines changed

12 files changed

+138
-3
lines changed

libcxx/docs/ReleaseNotes/22.rst

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

6970
- The performance of ``std::find`` has been improved by up to 2x for integral types
7071

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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -807,7 +807,11 @@ public:
807807

808808
template <_ContainerCompatibleRange<_Tp> _Range>
809809
_LIBCPP_HIDE_FROM_ABI void append_range(_Range&& __range) {
810-
insert_range(end(), std::forward<_Range>(__range));
810+
if constexpr (ranges::forward_range<_Range> || ranges::sized_range<_Range>) {
811+
__append_with_size(ranges::begin(__range), ranges::distance(__range));
812+
} else {
813+
__append_with_sentinel(ranges::begin(__range), ranges::end(__range));
814+
}
811815
}
812816
# endif
813817

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()" + 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()" + 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: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@ constexpr void for_all_iterators_and_allocators(Func f) {
8787
f.template operator()<Iter, sentinel_wrapper<Iter>, min_allocator<T>>();
8888
f.template operator()<Iter, sentinel_wrapper<Iter>, safe_allocator<T>>();
8989

90+
f.template operator()<Iter, sized_sentinel<Iter>, std::allocator<T>>();
91+
f.template operator()<Iter, sized_sentinel<Iter>, test_allocator<T>>();
92+
f.template operator()<Iter, sized_sentinel<Iter>, min_allocator<T>>();
93+
f.template operator()<Iter, sized_sentinel<Iter>, safe_allocator<T>>();
94+
9095
if constexpr (std::sentinel_for<Iter, Iter>) {
9196
f.template operator()<Iter, Iter, std::allocator<T>>();
9297
f.template operator()<Iter, Iter, test_allocator<T>>();

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: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,62 @@ 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+
684+
struct EmplaceConstructibleAndMoveInsertable {
685+
EmplaceConstructibleAndMoveInsertable(const EmplaceConstructibleAndMoveInsertable&) = delete;
686+
EmplaceConstructibleAndMoveInsertable& operator=(const EmplaceConstructibleAndMoveInsertable&) = delete;
687+
EmplaceConstructibleAndMoveInsertable& operator=(EmplaceConstructibleAndMoveInsertable&&) = delete;
688+
constexpr EmplaceConstructibleAndMoveInsertable(EmplaceConstructibleAndMoveInsertable&&) {}
689+
constexpr EmplaceConstructibleAndMoveInsertable(const InPlaceOnly&) {}
690+
};
691+
692+
template <template <class...> class Container>
693+
constexpr void test_sequence_append_range_emplace_constructible_and_move_insertable() {
694+
InPlaceOnly input[5];
695+
types::for_each(types::cpp20_input_iterator_list<InPlaceOnly*>{}, [&]<class Iter> {
696+
std::ranges::subrange in(Iter(input), sentinel_wrapper<Iter>(Iter(input + 5)));
697+
Container<EmplaceConstructibleAndMoveInsertable> c;
698+
c.append_range(in);
699+
});
700+
}
701+
646702
// Exception safety.
647703

648704
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)