Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement most ranges partition algorithms #976

Merged
merged 5 commits into from
Jul 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
330 changes: 318 additions & 12 deletions stl/inc/algorithm

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions tests/std/include/range_algorithm_support.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ namespace detail {
} // namespace detail
constexpr bool is_permissive = detail::Derived<int>::test();

template <class T>
inline constexpr T* nullptr_to = nullptr;

template <bool>
struct borrowed { // borrowed<true> is a borrowed_range; borrowed<false> is not
int* begin() const;
Expand Down
3 changes: 3 additions & 0 deletions tests/std/test.lst
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ tests\P0896R4_ranges_alg_minmax
tests\P0896R4_ranges_alg_mismatch
tests\P0896R4_ranges_alg_move
tests\P0896R4_ranges_alg_none_of
tests\P0896R4_ranges_alg_partition
tests\P0896R4_ranges_alg_partition_copy
tests\P0896R4_ranges_alg_partition_point
tests\P0896R4_ranges_alg_replace_if
StephanTLavavej marked this conversation as resolved.
Show resolved Hide resolved
tests\P0896R4_ranges_alg_search
tests\P0896R4_ranges_alg_search_n
Expand Down
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
115 changes: 115 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

// Covers ranges::is_partitioned and ranges::partition (and bits of ranges::partition_point)

#include <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <numeric>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

#define ASSERT(...) assert((__VA_ARGS__))

using P = pair<int, int>;

constexpr auto is_even = [](int i) { return i % 2 == 0; };

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::partition(borrowed<false>{}, is_even)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::partition(borrowed<true>{}, is_even)), ranges::subrange<int*>>);

struct empty_test {
template <ranges::input_range Range>
static constexpr void call() {
// Validate empty ranges
using ranges::is_partitioned, ranges::partition, ranges::partition_point;
{
Range range{};
ASSERT(is_partitioned(range, is_even, get_first));
}
{
Range range{};
ASSERT(is_partitioned(range.begin(), range.end(), is_even, get_first));
}

if constexpr (ranges::forward_range<Range>) {
const Range range{};
{
const auto result = partition(range, is_even, get_first);
ASSERT(result.begin() == range.end());
ASSERT(result.end() == range.end());
}
{
const auto result = partition(range.begin(), range.end(), is_even, get_first);
ASSERT(result.begin() == range.end());
ASSERT(result.end() == range.end());
}
}
}
};

struct partition_test {
static constexpr array elements = {P{0, 42}, P{1, 42}, P{2, 42}, P{3, 42}, P{4, 42}, P{5, 42}, P{6, 42}, P{7, 42}};

template <ranges::input_range Range>
static constexpr void call() {
using ranges::is_partitioned, ranges::partition, ranges::partition_point, ranges::is_permutation,
ranges::subrange;

auto pairs = elements;

{
Range range{pairs};
ASSERT(!is_partitioned(range, is_even, get_first));
}
{
Range range{pairs};
ASSERT(!is_partitioned(range.begin(), range.end(), is_even, get_first));
}

#if !defined(__clang__) && !defined(__EDG__) // TRANSITION, VSO-938163
if (!is_constant_evaluated())
#endif // TRANSITION, VSO-938163
{
if constexpr (ranges::forward_range<Range>) {
const Range range{pairs};
const auto mid = ranges::next(range.begin(), 4);

{
auto result = partition(range, is_even, get_first);
ASSERT(result.begin() == mid);
ASSERT(result.end() == range.end());
}
ASSERT(is_permutation(subrange{range.begin(), mid}, array{0, 2, 4, 6}, ranges::equal_to{}, get_first));
ASSERT(is_partitioned(range, is_even, get_first));
ASSERT(partition_point(range, is_even, get_first) == mid);

pairs = elements;

{
auto result = partition(range.begin(), range.end(), is_even, get_first);
ASSERT(result.begin() == mid);
ASSERT(result.end() == range.end());
}
ASSERT(is_permutation(subrange{range.begin(), mid}, array{0, 2, 4, 6}, ranges::equal_to{}, get_first));
ASSERT(is_partitioned(range.begin(), range.end(), is_even, get_first));
ASSERT(partition_point(range.begin(), range.end(), is_even, get_first) == mid);
}
}
}
};

int main() {
STATIC_ASSERT((test_in<empty_test, P>(), true));
test_in<empty_test, P>();

STATIC_ASSERT((test_in<partition_test, P>(), true));
test_in<partition_test, P>();
}
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_copy/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
134 changes: 134 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_copy/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <numeric>
#include <ranges>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

#define ASSERT(...) assert((__VA_ARGS__))

using P = pair<int, int>;

constexpr auto is_even = [](int i) { return i % 2 == 0; };

// Validate that partition_copy_result aliases in_out_out_result
STATIC_ASSERT(
same_as<ranges::partition_copy_result<int, double, void*>, ranges::in_out_out_result<int, double, void*>>);

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::partition_copy(borrowed<false>{}, nullptr_to<int>, nullptr_to<long>, is_even)),
ranges::partition_copy_result<ranges::dangling, int*, long*>>);
STATIC_ASSERT(same_as<decltype(ranges::partition_copy(borrowed<true>{}, nullptr_to<int>, nullptr_to<long>, is_even)),
ranges::partition_copy_result<int*, int*, long*>>);

struct empty_test {
template <ranges::input_range Range, indirectly_writable<ranges::range_reference_t<Range>> Out1,
indirectly_writable<ranges::range_reference_t<Range>> Out2>
static constexpr void call() {
// Validate empty ranges
using ranges::partition_copy, ranges::partition_copy_result, ranges::iterator_t;

{
Range range{};
auto result = partition_copy(range, Out1{}, Out2{}, is_even, get_first);
STATIC_ASSERT(same_as<decltype(partition_copy(range, Out1{}, Out2{}, is_even, get_first)),
partition_copy_result<iterator_t<Range>, Out1, Out2>>);
ASSERT(result.in == range.end());
ASSERT(result.out1.peek() == nullptr);
ASSERT(result.out2.peek() == nullptr);
}
{
Range range{};
auto result = partition_copy(range.begin(), range.end(), Out1{}, Out2{}, is_even, get_first);
STATIC_ASSERT(
same_as<decltype(partition_copy(range.begin(), range.end(), Out1{}, Out2{}, is_even, get_first)),
partition_copy_result<iterator_t<Range>, Out1, Out2>>);
ASSERT(result.in == range.end());
ASSERT(result.out1.peek() == nullptr);
ASSERT(result.out2.peek() == nullptr);
}
}
};

struct partition_copy_test {
static constexpr int N = 32;

template <ranges::input_range R, indirectly_writable<ranges::range_reference_t<R>> O1,
indirectly_writable<ranges::range_reference_t<R>> O2>
static constexpr void call() {
using ranges::partition_copy;

P source[N];
for (int i = 0; i < N; ++i) {
source[i] = {i, 42};
}

for (int i = 0; i < N; ++i) {
P dest[N];
ranges::fill(dest, P{-1, 13});

const R range{source};
auto result = partition_copy(
range, O1{dest}, O2{dest + i}, [i](int x) { return x < i; }, get_first);
assert(result.in == range.end());
assert(result.out1.peek() == dest + i);
assert(result.out2.peek() == dest + N);
assert(ranges::equal(source, dest));
}
}
};

template <class Instantiator, class Elem>
constexpr void run_tests() {
// Call Instantiator::template call</*...stuff...*/>() with a range whose element type is Elem, and two iterators to
// which Elem is writable, whose properties are "interesting" for ranges::partition_copy. What combinations of
// properties are "interesting"?

// For the input range, the algorithm simply unwraps iterators and chugs through looking for the end. It doesn't
// * take advantage of any capabilities provided by stronger-than-input categories,
// * care if the sentinel and iterator have the same type,
// * care if it can difference iterators with sentinels or each other, or
// * care about the size of the input range at all. (It can't even use size info to check the outputs, because we
// don't how many of the input elements will be written through each output.)
// TLDR: One input range with a proxy reference type and no other notable properties (the so-called "weakest" input
// range) suffices.

// For the outputs, both of which are treated equivalently, the algorithm is similarly oblivious to properties other
// than reference type and the ability to unwrap/rewrap. These could simply be the "weakest" writable iterator type
// in with_writable_iterators.

// Out of simple paranoia, let's permute ProxyRef; seven extra pointless tests won't hurt.

using test::range, test::iterator, test::input, test::output, test::CanCompare, test::CanDifference, test::Common,
test::ProxyRef, test::Sized;

using proxy_range = range<input, Elem, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::yes>;
using non_proxy_range = range<input, Elem, Sized::no, CanDifference::no, Common::no, CanCompare::no, ProxyRef::no>;
using proxy_iterator = iterator<output, remove_const_t<Elem>, CanDifference::no, CanCompare::no, ProxyRef::yes>;
using non_proxy_iterator = iterator<output, remove_const_t<Elem>, CanDifference::no, CanCompare::no, ProxyRef::no>;

Instantiator::template call<proxy_range, proxy_iterator, proxy_iterator>();
Instantiator::template call<proxy_range, proxy_iterator, non_proxy_iterator>();
Instantiator::template call<proxy_range, non_proxy_iterator, proxy_iterator>();
Instantiator::template call<proxy_range, non_proxy_iterator, non_proxy_iterator>();
Instantiator::template call<non_proxy_range, proxy_iterator, proxy_iterator>();
Instantiator::template call<non_proxy_range, proxy_iterator, non_proxy_iterator>();
Instantiator::template call<non_proxy_range, non_proxy_iterator, proxy_iterator>();
Instantiator::template call<non_proxy_range, non_proxy_iterator, non_proxy_iterator>();
}

int main() {
STATIC_ASSERT((run_tests<empty_test, const P>(), true));
run_tests<empty_test, const P>();

STATIC_ASSERT((run_tests<partition_copy_test, const P>(), true));
run_tests<partition_copy_test, const P>();
}
4 changes: 4 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_point/env.lst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Copyright (c) Microsoft Corporation.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

RUNALL_INCLUDE ..\concepts_matrix.lst
64 changes: 64 additions & 0 deletions tests/std/tests/P0896R4_ranges_alg_partition_point/test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <algorithm>
#include <array>
#include <cassert>
#include <concepts>
#include <numeric>
#include <ranges>
#include <span>
#include <utility>

#include <range_algorithm_support.hpp>

using namespace std;

#define ASSERT(...) assert((__VA_ARGS__))

using P = pair<int, int>;

constexpr auto is_even = [](int i) { return i % 2 == 0; };

// Validate dangling story
STATIC_ASSERT(same_as<decltype(ranges::partition_point(borrowed<false>{}, is_even)), ranges::dangling>);
STATIC_ASSERT(same_as<decltype(ranges::partition_point(borrowed<true>{}, is_even)), int*>);

struct empty_test {
template <ranges::forward_range Range>
static constexpr void call() {
// Validate empty ranges
using ranges::partition_point;

const Range range{};
ASSERT(partition_point(range, is_even, get_first) == range.end());
ASSERT(partition_point(range.begin(), range.end(), is_even, get_first) == range.end());
}
};

struct partition_point_test {
template <ranges::forward_range R>
static constexpr void call() {
using ranges::partition_point;
int elements[200];
iota(ranges::begin(elements), ranges::end(elements), 0);

// to avoid constant expression step limits
const size_t bound = elements[0] + (is_constant_evaluated() ? 10 : ranges::size(elements));

for (size_t i = 0; i < bound; ++i) {
const R range{span{elements}.first(i)};
for (size_t j = 0; j < i; ++j) {
assert(partition_point(range, [j](int x) { return x < static_cast<int>(j); }).peek() == elements + j);
}
}
}
};

int main() {
STATIC_ASSERT((test_fwd<empty_test, P>(), true));
test_fwd<empty_test, P>();

STATIC_ASSERT((test_fwd<partition_point_test, const int>(), true));
test_fwd<partition_point_test, const int>();
}