//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef SUPPORT_PUSH_RANGE_CONTAINER_ADAPTORS_H
#define SUPPORT_PUSH_RANGE_CONTAINER_ADAPTORS_H
#include <algorithm>
#include <cassert>
#include <concepts>
#include <cstddef>
#include <initializer_list>
#include <ranges>
#include <type_traits>
#include <vector>
#include "../exception_safety_helpers.h"
#include "../from_range_helpers.h"
#include "../insert_range_helpers.h"
#include "MoveOnly.h"
#include "almost_satisfies_types.h"
#include "count_new.h"
#include "min_allocator.h"
#include "test_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
#include "type_algorithms.h"
#include "unwrap_container_adaptor.h"
template <class Container, class Range>
concept HasPushRange = requires (Container& c, Range&& range) {
c.push_range(range);
};
template <template <class...> class Container, class T, class U>
constexpr bool test_constraints_push_range() {
// Input range with the same value type.
static_assert(HasPushRange<Container<T>, InputRange<T>>);
// Input range with a convertible value type.
static_assert(HasPushRange<Container<T>, InputRange<U>>);
// Input range with a non-convertible value type.
static_assert(!HasPushRange<Container<T>, InputRange<Empty>>);
// Not an input range.
static_assert(!HasPushRange<Container<T>, InputRangeNotDerivedFrom>);
static_assert(!HasPushRange<Container<T>, InputRangeNotIndirectlyReadable>);
static_assert(!HasPushRange<Container<T>, InputRangeNotInputOrOutputIterator>);
return true;
}
// Empty container.
template <class T>
TestCase<T> constexpr EmptyContainer_EmptyRange {
.initial = {}, .input = {}, .expected = {}
};
template <class T> constexpr TestCase<T> EmptyContainer_OneElementRange {
.initial = {}, .input = {5}, .expected = {5}
};
template <class T> constexpr TestCase<T> EmptyContainer_MidRange {
.initial = {}, .input = {5, 3, 1, 7, 9}, .expected = {5, 3, 1, 7, 9}
};
// One-element container.
template <class T> constexpr TestCase<T> OneElementContainer_EmptyRange {
.initial = {3}, .input = {}, .expected = {3}
};
template <class T> constexpr TestCase<T> OneElementContainer_OneElementRange {
.initial = {3}, .input = {-5}, .expected = {3, -5}
};
template <class T> constexpr TestCase<T> OneElementContainer_MidRange {
.initial = {3}, .input = {-5, -3, -1, -7, -9}, .expected = {3, -5, -3, -1, -7, -9}
};
// Full container.
template <class T> constexpr TestCase<T> FullContainer_EmptyRange {
.initial = {11, 29, 35, 14, 84}, .input = {}, .expected = {11, 29, 35, 14, 84}
};
template <class T> constexpr TestCase<T> FullContainer_OneElementRange {
.initial = {11, 29, 35, 14, 84}, .input = {-5}, .expected = {11, 29, 35, 14, 84, -5}
};
template <class T> constexpr TestCase<T> FullContainer_MidRange {
.initial = {11, 29, 35, 14, 84},
.input = {-5, -3, -1, -7, -9},
.expected = {11, 29, 35, 14, 84, -5, -3, -1, -7, -9}
};
template <class T> constexpr TestCase<T> FullContainer_LongRange {
.initial = {11, 29, 35, 14, 84},
.input = {-5, -3, -1, -7, -9, -19, -48, -56, -13, -14, -29, -88, -17, -1, -5, -11, -89, -21, -33, -48},
.expected = {
11, 29, 35, 14, 84, -5, -3, -1, -7, -9, -19, -48, -56, -13, -14, -29, -88, -17, -1, -5, -11, -89, -21, -33, -48
}
};
// Container adaptors tests.
template <class Adaptor, class Iter, class Sent>
constexpr void test_push_range(bool is_result_heapified = false) {
using T = typename Adaptor::value_type;
auto test = [&](auto& test_case) {
Adaptor adaptor(test_case.initial.begin(), test_case.initial.end());
auto in = wrap_input<Iter, Sent>(test_case.input);
adaptor.push_range(in);
UnwrapAdaptor<Adaptor> unwrap_adaptor(std::move(adaptor));
auto& c = unwrap_adaptor.get_container();
if (is_result_heapified) {
assert(std::ranges::is_heap(c));
return std::ranges::is_permutation(c, test_case.expected);
} else {
return std::ranges::equal(c, test_case.expected);
}
};
{ // Empty container.
// empty_c.push_range(empty_range)
assert(test(EmptyContainer_EmptyRange<T>));
// empty_c.push_range(one_element_range)
assert(test(EmptyContainer_OneElementRange<T>));
// empty_c.push_range(mid_range)
assert(test(EmptyContainer_MidRange<T>));
}
{ // One-element container.
// one_element_c.push_range(empty_range)
assert(test(OneElementContainer_EmptyRange<T>));
// one_element_c.push_range(one_element_range)
assert(test(OneElementContainer_OneElementRange<T>));
// one_element_c.push_range(mid_range)
assert(test(OneElementContainer_MidRange<T>));
}
{ // Full container.
// full_container.push_range(empty_range)
assert(test(FullContainer_EmptyRange<T>));
// full_container.push_range(one_element_range)
assert(test(FullContainer_OneElementRange<T>));
// full_container.push_range(mid_range)
assert(test(FullContainer_MidRange<T>));
// full_container.push_range(long_range)
assert(test(FullContainer_LongRange<T>));
}
}
// Move-only types.
template <template <class ...> class Container>
constexpr void test_push_range_move_only() {
MoveOnly input[5];
std::ranges::subrange in(std::move_iterator{input}, std::move_iterator{input + 5});
Container<MoveOnly> c;
c.push_range(in);
}
// Check that `append_range` is preferred if available and `push_back` is used as a fallback.
enum class InserterChoice {
Invalid,
PushBack,
AppendRange
};
template <class T, InserterChoice Inserter>
struct Container {
InserterChoice inserter_choice = InserterChoice::Invalid;
using value_type = T;
using iterator = T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
static constexpr int Capacity = 8;
int size_ = 0;
value_type buffer_[Capacity] = {};
iterator begin() { return buffer_; }
iterator end() { return buffer_ + size_; }
size_type size() const { return size_; }
template <class U>
void push_back(U val)
requires (Inserter >= InserterChoice::PushBack) {
inserter_choice = InserterChoice::PushBack;
buffer_[size_] = val;
++size_;
}
template <std::ranges::input_range Range>
void append_range(Range&& range)
requires (Inserter >= InserterChoice::AppendRange) {
assert(size() + std::ranges::distance(range) <= Capacity);
inserter_choice = InserterChoice::AppendRange;
for (auto&& e : range) {
buffer_[size_] = e;
++size_;
}
}
friend bool operator==(const Container&, const Container&) = default;
};
template <template <class ...> class AdaptorT, class T>
void test_push_range_inserter_choice(bool is_result_heapified = false) {
{ // `append_range` is preferred if available.
using BaseContainer = Container<T, InserterChoice::AppendRange>;
using Adaptor = AdaptorT<T, BaseContainer>;
T in[] = {1, 2, 3, 4, 5};
Adaptor adaptor;
adaptor.push_range(in);
UnwrapAdaptor<Adaptor> unwrap_adaptor(std::move(adaptor));
auto& c = unwrap_adaptor.get_container();
assert(c.inserter_choice == InserterChoice::AppendRange);
if (is_result_heapified) {
assert(std::ranges::is_heap(c));
assert(std::ranges::is_permutation(c, in));
} else {
assert(std::ranges::equal(c, in));
}
}
{ // `push_back` is used as a fallback (via `back_inserter`).
using BaseContainer = Container<T, InserterChoice::PushBack>;
using Adaptor = AdaptorT<T, BaseContainer>;
T in[] = {1, 2, 3, 4, 5};
Adaptor adaptor;
adaptor.push_range(in);
UnwrapAdaptor<Adaptor> unwrap_adaptor(std::move(adaptor));
auto& c = unwrap_adaptor.get_container();
assert(c.inserter_choice == InserterChoice::PushBack);
if (is_result_heapified) {
assert(std::ranges::is_heap(c));
assert(std::ranges::is_permutation(c, in));
} else {
assert(std::ranges::equal(c, in));
}
}
}
// Exception safety.
template <template <class ...> class Container>
void test_push_range_exception_safety_throwing_copy() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
constexpr int ThrowOn = 3;
using T = ThrowingCopy<ThrowOn>;
test_exception_safety_throwing_copy<ThrowOn, /*Size=*/5>([](auto* from, auto* to) {
Container<T> c;
c.push_range(std::ranges::subrange(from, to));
});
#endif
}
template <template <class ...> class Adaptor, template <class ...> class BaseContainer, class T>
void test_push_range_exception_safety_throwing_allocator() {
#if !defined(TEST_HAS_NO_EXCEPTIONS)
T in[] = {0, 1};
try {
globalMemCounter.reset();
Adaptor<T, BaseContainer<T, ThrowingAllocator<T>>> c;
c.push_range(in);
assert(false); // The function call above should throw.
} catch (int) {
assert(globalMemCounter.new_called == globalMemCounter.delete_called);
}
#endif
}
#endif // SUPPORT_PUSH_RANGE_CONTAINER_ADAPTORS_H