[libcxx-commits] [libcxx] [libc++] Avoid materializing input ranges prepended to deque (PR #199969)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed May 27 05:33:02 PDT 2026
https://github.com/BITree2004 created https://github.com/llvm/llvm-project/pull/199969
Fixes #199788
Avoid materializing unsized input ranges when inserting at the beginning of std::deque.
For input iterator insertion at begin() and C++23 insert_range/prepend_range, deque now prepends elements directly and reverses the inserted prefix to preserve range order. The existing split_buffer path remains the fallback when value_type is not swappable.
Testing:
- input_range_insert_at_begin.pass.cpp: C++11/14/17/20/23
- input_range_insert_at_begin.pass.cpp: C++23 no-exceptions and ASan
- prepend_range.pass.cpp: C++23 normal/no-exceptions/ASan
- insert_range.pass.cpp: C++23 normal/no-exceptions/ASan
- insert_iter_iter.pass.cpp: C++03 and C++23
- benchmark syntax check for deque/list/vector
- git diff --check
>From d92d397cfaaf75e4a3c11cd888fc922de950b41f Mon Sep 17 00:00:00 2001
From: bitree <bitree2004 at yandex.ru>
Date: Wed, 27 May 2026 14:43:26 +0300
Subject: [PATCH] [libc++] Avoid materializing input ranges prepended to deque
---
libcxx/include/deque | 57 +++++++++++
.../sequence/sequence_container_benchmarks.h | 22 +++++
.../input_range_insert_at_begin.pass.cpp | 94 +++++++++++++++++++
.../deque.modifiers/prepend_range.pass.cpp | 78 +++++++++++++++
4 files changed, 251 insertions(+)
create mode 100644 libcxx/test/libcxx/containers/sequences/deque/input_range_insert_at_begin.pass.cpp
diff --git a/libcxx/include/deque b/libcxx/include/deque
index c8c6889f1a165..a8716920134ba 100644
--- a/libcxx/include/deque
+++ b/libcxx/include/deque
@@ -194,6 +194,7 @@ template <class T, class Allocator, class Predicate>
# include <__algorithm/ranges_copy_n.h>
# include <__algorithm/remove.h>
# include <__algorithm/remove_if.h>
+# include <__algorithm/reverse.h>
# include <__assert>
# include <__config>
# include <__debug_utils/sanitizers.h>
@@ -224,6 +225,7 @@ template <class T, class Allocator, class Predicate>
# include <__type_traits/conditional.h>
# include <__type_traits/container_traits.h>
# include <__type_traits/enable_if.h>
+# include <__type_traits/integral_constant.h>
# include <__type_traits/is_allocator.h>
# include <__type_traits/is_convertible.h>
# include <__type_traits/is_nothrow_assignable.h>
@@ -871,6 +873,8 @@ public:
return __insert_with_size(__position, ranges::begin(__range), __n);
} else {
+ if (__position == cbegin())
+ return __prepend_with_sentinel(ranges::begin(__range), ranges::end(__range));
return __insert_with_sentinel(__position, ranges::begin(__range), ranges::end(__range));
}
}
@@ -1231,6 +1235,15 @@ private:
template <class _Iterator, class _Sentinel>
_LIBCPP_HIDE_FROM_ABI iterator __insert_with_sentinel(const_iterator __p, _Iterator __f, _Sentinel __l);
+# ifndef _LIBCPP_CXX03_LANG
+ template <class _Iterator, class _Sentinel>
+ _LIBCPP_HIDE_FROM_ABI iterator __prepend_with_sentinel(_Iterator __f, _Sentinel __l);
+ template <class _Iterator, class _Sentinel>
+ _LIBCPP_HIDE_FROM_ABI iterator __prepend_with_sentinel(_Iterator __f, _Sentinel __l, true_type);
+ template <class _Iterator, class _Sentinel>
+ _LIBCPP_HIDE_FROM_ABI iterator __prepend_with_sentinel(_Iterator __f, _Sentinel __l, false_type);
+# endif
+
template <class _Iterator>
_LIBCPP_HIDE_FROM_ABI iterator __insert_with_size(const_iterator __p, _Iterator __f, size_type __n);
@@ -1784,6 +1797,11 @@ template <class _Tp, class _Allocator>
template <class _InputIter, __enable_if_t<__has_exactly_input_iterator_category<_InputIter>::value, int> >
typename deque<_Tp, _Allocator>::iterator
deque<_Tp, _Allocator>::insert(const_iterator __p, _InputIter __f, _InputIter __l) {
+# ifndef _LIBCPP_CXX03_LANG
+ if (__p == cbegin())
+ return __prepend_with_sentinel(__f, __l);
+# endif
+
return __insert_with_sentinel(__p, __f, __l);
}
@@ -1797,6 +1815,45 @@ deque<_Tp, _Allocator>::__insert_with_sentinel(const_iterator __p, _Iterator __f
return insert(__p, move_iterator<__bi>(__buf.begin()), move_iterator<__bi>(__buf.end()));
}
+# ifndef _LIBCPP_CXX03_LANG
+template <class _Tp, class _Allocator>
+template <class _Iterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI typename deque<_Tp, _Allocator>::iterator
+deque<_Tp, _Allocator>::__prepend_with_sentinel(_Iterator __f, _Sentinel __l) {
+ return __prepend_with_sentinel(
+ std::move(__f), std::move(__l), integral_constant<bool, __is_swappable_v<value_type> >());
+}
+
+template <class _Tp, class _Allocator>
+template <class _Iterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI typename deque<_Tp, _Allocator>::iterator
+deque<_Tp, _Allocator>::__prepend_with_sentinel(_Iterator __f, _Sentinel __l, true_type) {
+ size_type __inserted = 0;
+ auto __guard = std::__make_exception_guard([&] {
+ while (__inserted > 0) {
+ pop_front();
+ --__inserted;
+ }
+ });
+
+ for (; __f != __l; ++__f) {
+ emplace_front(*__f);
+ ++__inserted;
+ }
+ std::reverse(begin(), begin() + __inserted);
+
+ __guard.__complete();
+ return begin();
+}
+
+template <class _Tp, class _Allocator>
+template <class _Iterator, class _Sentinel>
+_LIBCPP_HIDE_FROM_ABI typename deque<_Tp, _Allocator>::iterator
+deque<_Tp, _Allocator>::__prepend_with_sentinel(_Iterator __f, _Sentinel __l, false_type) {
+ return __insert_with_sentinel(cbegin(), std::move(__f), std::move(__l));
+}
+# endif
+
template <class _Tp, class _Allocator>
template <class _ForwardIterator, __enable_if_t<__has_exactly_forward_iterator_category<_ForwardIterator>::value, int> >
typename deque<_Tp, _Allocator>::iterator
diff --git a/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h b/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
index 92346c38563fb..4373cb44dc917 100644
--- a/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
+++ b/libcxx/test/benchmarks/containers/sequence/sequence_container_benchmarks.h
@@ -447,6 +447,28 @@ void sequence_container_benchmarks(std::string container) {
state.ResumeTiming();
}
});
+
+ for (auto gen : generators)
+ bench("prepend_range(input-iter) (full container)" + tostr(gen), [gen](auto& state) {
+ auto const size = state.range(0);
+ std::vector<ValueType> in;
+ std::generate_n(std::back_inserter(in), size, gen);
+ DoNotOptimizeData(in);
+
+ Container c(in.begin(), in.end());
+ DoNotOptimizeData(c);
+ for (auto _ : state) {
+ using Iter = cpp20_input_iterator<ValueType*>;
+ auto first = Iter(in.data());
+ auto last = sentinel_wrapper<Iter>(Iter(in.data() + in.size()));
+ c.prepend_range(std::ranges::subrange(std::move(first), std::move(last)));
+ DoNotOptimizeData(c);
+
+ state.PauseTiming();
+ c.erase(c.begin(), std::next(c.begin(), size));
+ state.ResumeTiming();
+ }
+ });
}
/////////////////////////
diff --git a/libcxx/test/libcxx/containers/sequences/deque/input_range_insert_at_begin.pass.cpp b/libcxx/test/libcxx/containers/sequences/deque/input_range_insert_at_begin.pass.cpp
new file mode 100644
index 0000000000000..7d5794faa551a
--- /dev/null
+++ b/libcxx/test/libcxx/containers/sequences/deque/input_range_insert_at_begin.pass.cpp
@@ -0,0 +1,94 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Check that inserting an unsized input range at the start of a deque uses
+// existing front spare capacity instead of materializing the range first.
+
+// UNSUPPORTED: c++03
+
+#include <cassert>
+#include <deque>
+
+#include "count_new.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+#if TEST_STD_VER >= 23
+# include <ranges>
+#endif
+
+std::deque<int> make_deque_with_front_spare() {
+ std::deque<int> c;
+ for (int i = 0; i != 8; ++i)
+ c.push_back(100 + i);
+ for (int i = 0; i != 4; ++i)
+ c.pop_front();
+ return c;
+}
+
+void assert_expected(const std::deque<int>& c) {
+ int expected[] = {0, 1, 2, 3, 104, 105, 106, 107};
+ assert(c.size() == 8);
+ for (int i = 0; i != 8; ++i)
+ assert(c[i] == expected[i]);
+}
+
+void test_insert_iter_iter() {
+ std::deque<int> c = make_deque_with_front_spare();
+ int input[] = {0, 1, 2, 3};
+ typedef cpp17_input_iterator<int*> Iter;
+
+ {
+ DisableAllocationGuard guard;
+ auto it = c.insert(c.begin(), Iter(input), Iter(input + 4));
+ assert(it == c.begin());
+ }
+
+ assert_expected(c);
+}
+
+#if TEST_STD_VER >= 23
+void test_insert_range() {
+ std::deque<int> c = make_deque_with_front_spare();
+ int input[] = {0, 1, 2, 3};
+ using Iter = cpp20_input_iterator<int*>;
+ auto in = std::ranges::subrange(Iter(input), sentinel_wrapper<Iter>(Iter(input + 4)));
+
+ {
+ DisableAllocationGuard guard;
+ auto it = c.insert_range(c.begin(), in);
+ assert(it == c.begin());
+ }
+
+ assert_expected(c);
+}
+
+void test_prepend_range() {
+ std::deque<int> c = make_deque_with_front_spare();
+ int input[] = {0, 1, 2, 3};
+ using Iter = cpp20_input_iterator<int*>;
+ auto in = std::ranges::subrange(Iter(input), sentinel_wrapper<Iter>(Iter(input + 4)));
+
+ {
+ DisableAllocationGuard guard;
+ c.prepend_range(in);
+ }
+
+ assert_expected(c);
+}
+#endif
+
+int main(int, char**) {
+ test_insert_iter_iter();
+#if TEST_STD_VER >= 23
+ test_insert_range();
+ test_prepend_range();
+#endif
+
+ return 0;
+}
diff --git a/libcxx/test/std/containers/sequences/deque/deque.modifiers/prepend_range.pass.cpp b/libcxx/test/std/containers/sequences/deque/deque.modifiers/prepend_range.pass.cpp
index 5ff572c79e512..cfd5135bad50c 100644
--- a/libcxx/test/std/containers/sequences/deque/deque.modifiers/prepend_range.pass.cpp
+++ b/libcxx/test/std/containers/sequences/deque/deque.modifiers/prepend_range.pass.cpp
@@ -17,6 +17,82 @@
#include "../../insert_range_sequence_containers.h"
#include "test_macros.h"
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+struct ThrowingSwap {
+ static bool throwing_enabled;
+ static int swaps;
+
+ int value = 0;
+
+ ThrowingSwap() = default;
+ ThrowingSwap(int v) : value(v) {}
+
+ friend void swap(ThrowingSwap& lhs, ThrowingSwap& rhs) {
+ int tmp = lhs.value;
+ lhs.value = rhs.value;
+ rhs.value = tmp;
+ if (throwing_enabled && ++swaps == 2)
+ throw 1;
+ }
+
+ friend bool operator==(const ThrowingSwap& lhs, const ThrowingSwap& rhs) { return lhs.value == rhs.value; }
+};
+
+bool ThrowingSwap::throwing_enabled = false;
+int ThrowingSwap::swaps = 0;
+#endif
+
+void test_input_range_strong_exception_safety() {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+ using T = ThrowingCopy<3>;
+
+ T::throwing_enabled = false;
+ std::deque<T> c;
+ for (int i = 0; i != 8; ++i)
+ c.emplace_back(100 + i);
+ for (int i = 0; i != 4; ++i)
+ c.pop_front();
+ std::deque<T> expected = c;
+
+ T input[] = {T(0), T(1), T(2), T(3)};
+ using Iter = cpp20_input_iterator<T*>;
+ auto in = std::ranges::subrange(Iter(input), sentinel_wrapper<Iter>(Iter(input + 4)));
+
+ T::reset();
+ T::throwing_enabled = true;
+ try {
+ c.prepend_range(in);
+ assert(false);
+ } catch (int) {
+ assert(c == expected);
+ }
+ T::throwing_enabled = false;
+#endif
+}
+
+void test_input_range_reverse_exception_safety() {
+#if !defined(TEST_HAS_NO_EXCEPTIONS)
+ std::deque<ThrowingSwap> c = {100, 101, 102, 103, 104, 105, 106, 107};
+ for (int i = 0; i != 4; ++i)
+ c.pop_front();
+ std::deque<ThrowingSwap> expected = c;
+
+ ThrowingSwap input[] = {0, 1, 2, 3};
+ using Iter = cpp20_input_iterator<ThrowingSwap*>;
+ auto in = std::ranges::subrange(Iter(input), sentinel_wrapper<Iter>(Iter(input + 4)));
+
+ ThrowingSwap::swaps = 0;
+ ThrowingSwap::throwing_enabled = true;
+ try {
+ c.prepend_range(in);
+ assert(false);
+ } catch (int) {
+ assert(c == expected);
+ }
+ ThrowingSwap::throwing_enabled = false;
+#endif
+}
+
// Tested cases:
// - different kinds of insertions (prepending an {empty/one-element/mid-sized/long range} into an
// {empty/one-element/full} container);
@@ -36,6 +112,8 @@ int main(int, char**) {
test_prepend_range_exception_safety_throwing_copy<std::deque>();
test_prepend_range_exception_safety_throwing_allocator<std::deque, int>();
+ test_input_range_strong_exception_safety();
+ test_input_range_reverse_exception_safety();
return 0;
}
More information about the libcxx-commits
mailing list