utils: chunked_vector: add from_range_t constructor

std::ranges::to<> has a little protocol with containers. Implement it
to get optimized construction.

Similar to the iterator pair constructor, if the range's size can be
obtained (even with an O(N) algorithm), favor that to avoid reallocations.
Copy elements spanwise to promote optimization to memcpy when possible.
This commit is contained in:
Avi Kivity
2024-10-31 19:32:16 +02:00
parent b2769403d2
commit 6a9852d47b
2 changed files with 47 additions and 0 deletions

View File

@@ -178,6 +178,19 @@ BOOST_AUTO_TEST_CASE(tests_constructor_exception_safety) {
}
}
BOOST_AUTO_TEST_CASE(tests_constructor_exception_safety_range) {
auto checker = exception_safety_checker();
auto v = std::vector<exception_safe_class>(100, exception_safe_class(checker));
checker.set_countdown(5);
try {
auto u = utils::chunked_vector<exception_safe_class, 128>(std::from_range, v);
BOOST_REQUIRE(false);
} catch (...) {
v.clear();
BOOST_REQUIRE(checker.ok());
}
}
BOOST_AUTO_TEST_CASE(tests_reserve_partial) {
auto rand = std::default_random_engine();
auto size_dist = std::uniform_int_distribution<unsigned>(1, 1 << 12);
@@ -429,6 +442,13 @@ BOOST_AUTO_TEST_CASE(test_initializer_list_ctor) {
BOOST_REQUIRE(vit == vec.end());
}
BOOST_AUTO_TEST_CASE(test_range_ctor) {
auto range = std::views::iota(0, 12345);
auto vec = range | std::ranges::to<utils::chunked_vector<int, 512>>();
BOOST_REQUIRE(std::ranges::equal(range, vec));
}
BOOST_AUTO_TEST_CASE(test_value_default_init_ctor) {
int n = 17;
auto vec = utils::chunked_vector<std::string, 8>(n);

View File

@@ -104,6 +104,11 @@ public:
chunked_vector(chunked_vector&& x) noexcept;
template <typename Iterator>
chunked_vector(Iterator begin, Iterator end);
template <std::ranges::range Range>
requires std::convertible_to<std::ranges::range_value_t<Range>, T>
chunked_vector(std::from_range_t, Range&& range);
chunked_vector(std::initializer_list<T> x);
explicit chunked_vector(size_t n, const T& value = T());
~chunked_vector();
@@ -372,6 +377,28 @@ chunked_vector<T, max_contiguous_allocation>::chunked_vector(Iterator begin, Ite
}
}
template <typename T, size_t max_contiguous_allocation>
template <std::ranges::range Range>
requires std::convertible_to<std::ranges::range_value_t<Range>, T>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(std::from_range_t, Range&& range)
: chunked_vector() {
if constexpr (std::ranges::forward_range<Range>) {
size_t size = std::ranges::distance(range);
reserve(size);
auto begin = std::ranges::begin(range);
for (size_t i = 0; _size < size; ++i) {
T* dst = _chunks[i].get();
auto now = std::min(size - _size, max_chunk_capacity());
begin = std::ranges::uninitialized_copy_n(begin, now, dst, dst + now).in;
// Update _size incrementally to let the destructor
// know how much data to destroy on exception
_size += now;
}
} else {
std::ranges::copy(range, std::back_inserter(*this));
}
}
template <typename T, size_t max_contiguous_allocation>
chunked_vector<T, max_contiguous_allocation>::chunked_vector(std::initializer_list<T> x)
: chunked_vector(std::begin(x), std::end(x)) {