diff --git a/experimental/segmented_all_of_test.cpp b/experimental/segmented_all_of_test.cpp index d1dd0c6..8055b9d 100644 --- a/experimental/segmented_all_of_test.cpp +++ b/experimental/segmented_all_of_test.cpp @@ -272,6 +272,66 @@ void test_all_of_forward_failure_before_last_segment() BOOST_TEST(segmented_all_of(sv.begin(), last, not_equals_val(80))); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.all.of] mandates "At most last - first applications of the predicate". +// The lower bound is not in the standard but follows from the answer: false +// cannot be returned without having applied pred to every element up to and +// including the first failing one, and true cannot be returned without having +// applied it to all of them. Bracketing the count catches an element retested +// when the scan crosses a segment boundary. +////////////////////////////////////////////////////////////////////////////// + +template +struct all_of_count_check +{ + Pred pred; + + explicit all_of_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const boost::container::vector flat = test_detail::flatten_n_ints(c, n); + std::size_t needed = n; + for(std::size_t i = 0; i != n; ++i) + if(!pred(flat[i])) { needed = i + 1u; break; } + + test_detail::op_counter calls; + segmented_all_of(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(calls.n >= needed); + BOOST_TEST(spec != 0); + } +}; + +template +void run_all_of_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, all_of_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, all_of_count_check(pred)); +} + +void test_all_of_predicate_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_all_of_count_shapes(vals, n, not_equals_val((v <= n) ? int(v) : -999)); + run_all_of_count_shapes(vals, n, not_multiple_of(1)); + run_all_of_count_shapes(vals, n, not_multiple_of(3)); + run_all_of_count_shapes(vals, n, is_positive()); + } +} + int main() { test_all_of_shape_matrix(); @@ -285,5 +345,6 @@ int main() test_all_of_single_segment_seg2_single_inner(); test_all_of_single_segment_forward(); test_all_of_forward_failure_before_last_segment(); + test_all_of_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_any_of_test.cpp b/experimental/segmented_any_of_test.cpp index d654fe6..c3d0b6a 100644 --- a/experimental/segmented_any_of_test.cpp +++ b/experimental/segmented_any_of_test.cpp @@ -272,6 +272,66 @@ void test_any_of_forward_hit_before_last_segment() BOOST_TEST(!segmented_any_of(sv.begin(), last, equals_val(80))); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.any.of] mandates "At most last - first applications of the predicate". +// The lower bound is not in the standard but follows from the answer: true +// cannot be returned without having applied pred to every element up to and +// including the first hit, and false cannot be returned without having applied +// it to all of them. Bracketing the count catches an element retested when +// the scan crosses a segment boundary. +////////////////////////////////////////////////////////////////////////////// + +template +struct any_of_count_check +{ + Pred pred; + + explicit any_of_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const boost::container::vector flat = test_detail::flatten_n_ints(c, n); + std::size_t needed = n; + for(std::size_t i = 0; i != n; ++i) + if(pred(flat[i])) { needed = i + 1u; break; } + + test_detail::op_counter calls; + segmented_any_of(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(calls.n >= needed); + BOOST_TEST(spec != 0); + } +}; + +template +void run_any_of_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, any_of_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, any_of_count_check(pred)); +} + +void test_any_of_predicate_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_any_of_count_shapes(vals, n, equals_val((v <= n) ? int(v) : -999)); + run_any_of_count_shapes(vals, n, multiple_of(1)); + run_any_of_count_shapes(vals, n, multiple_of(3)); + run_any_of_count_shapes(vals, n, is_negative()); + } +} + int main() { test_any_of_shape_matrix(); @@ -285,5 +345,6 @@ int main() test_any_of_single_segment_seg2_single_inner(); test_any_of_single_segment_forward(); test_any_of_forward_hit_before_last_segment(); + test_any_of_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_copy_if_test.cpp b/experimental/segmented_copy_if_test.cpp index 33925b7..99359b4 100644 --- a/experimental/segmented_copy_if_test.cpp +++ b/experimental/segmented_copy_if_test.cpp @@ -586,6 +586,95 @@ void test_copy_if_unrolled_blocks() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.copy] mandates "Exactly last - first applications of the corresponding +// predicate", whatever the segmentation of either range. A leaf that applies +// pred, then finds the destination segment full and returns without consuming +// the element it has just tested hands that element back to the destination +// walker, which re-applies pred to it; only an equality catches that. +////////////////////////////////////////////////////////////////////////////// + +struct copy_if_count_check +{ + template + void operator()(CSrc& src, std::size_t n_src, const char* src_spec, + CDst& dst, std::size_t n_dst, const char* dst_spec) const + { + test_detail::op_counter calls; + segmented_copy_if(src.begin(), test_detail::iter_at(src, n_src), dst.begin(), + test_detail::counting_pred(calls, is_even())); + + BOOST_TEST_EQ(calls.n, n_src); + BOOST_TEST(test_detail::filler_intact(dst, n_dst, shape_filler)); + BOOST_TEST(src_spec != 0 && dst_spec != 0); + } +}; + +void copy_if_add_block(test_detail::seg_vector& c, std::size_t block) +{ c.add_segment(block, 0); } + +void copy_if_add_block(test_detail::seg2_vector& c, std::size_t block) +{ + test_detail::seg_vector inner; + inner.add_segment(block, 0); + c.add_segment(inner); +} + +// Destination segments of a handful of elements each, so that boundary +// crossings dominate: with 64 elements and blocks of 8 there are seven of them, +// and the depth-2 destination multiplies them. +template +void copy_if_count_small_dst(std::size_t n, std::size_t block, bool all_match) +{ + boost::container::vector src; + src.reserve(n); + for(std::size_t i = 0; i != n; ++i) + src.push_back(all_match ? 2*static_cast(i) + 2 : 2*static_cast(i) + 1); + + CDst dst; + for(std::size_t room = 0; room < n; room += block) + copy_if_add_block(dst, block); + + test_detail::op_counter calls; + segmented_copy_if(src.begin(), src.end(), dst.begin(), + test_detail::counting_pred(calls, is_even())); + BOOST_TEST_EQ(calls.n, n); +} + +void test_copy_if_predicate_count() +{ + static const int mixed[] = {2, 7, 4, 9, 6, 11, 8, 13, 10, 15}; + static const int all_hit[] = {2, 4, 6, 8, 10, 12}; + static const int no_hit[] = {1, 3, 5, 7, 9, 11}; + static const int* const sets[] = {mixed, all_hit, no_hit}; + static const std::size_t set_len[] = {10u, 6u, 6u}; + static const std::size_t sizes[] = {0u, 1u, 2u, 3u, 5u, 6u}; + + for(std::size_t s = 0; s != sizeof(sets)/sizeof(sets[0]); ++s) { + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) { + const std::size_t n_src = sizes[i]; + if(n_src > set_len[s]) + continue; + const std::size_t m = shape_match_count(sets[s], n_src); + test_detail::for_each_shape2_all + (sets[s], n_src, shape_dst_vals, m, shape_filler, copy_if_count_check()); + test_detail::for_each_shape2_all + (sets[s], n_src, shape_dst_vals, m + 3u, shape_filler, copy_if_count_check()); + } + } + + static const std::size_t blocks[] = {1u, 2u, 3u, 8u, 16u}; + for(std::size_t b = 0; b != sizeof(blocks)/sizeof(blocks[0]); ++b) { + for(int am = 0; am != 2; ++am) { + const bool a = am != 0; + copy_if_count_small_dst >(64u, blocks[b], a); + copy_if_count_small_dst >(64u, blocks[b], a); + } + } +} + int main() { test_copy_if_full_range(); @@ -610,5 +699,6 @@ int main() test_copy_if_single_segment_dst_from_flat(); test_copy_if_shape_matrix(); test_copy_if_unrolled_blocks(); + test_copy_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_count_if_test.cpp b/experimental/segmented_count_if_test.cpp index acdadbb..850cc1f 100644 --- a/experimental/segmented_count_if_test.cpp +++ b/experimental/segmented_count_if_test.cpp @@ -295,6 +295,41 @@ void test_count_if_forward_multi_segment() BOOST_TEST_EQ(segmented_count_if(sv.begin(), test_detail::iter_at(sv, 7), is_negative()), 0); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.count] mandates "Exactly last - first applications of the corresponding +// predicate", so a segment walked twice or an element skipped shows up here +// even in the cases where the returned total still comes out right. +////////////////////////////////////////////////////////////////////////////// + +struct count_if_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_count_if(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, is_even())); + + BOOST_TEST_EQ(calls.n, n); + BOOST_TEST(spec != 0); + } +}; + +void test_count_if_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16] = { 0 }; + for(std::size_t i = 0; i != n; ++i) + vals[i] = int(i) + 1; + test_detail::for_each_shape_all(vals, n, -999, count_if_count_check()); + test_detail::for_each_shape_all_fwd(vals, n, -999, count_if_count_check()); + } +} + int main() { test_count_if_shape_matrix(); @@ -310,5 +345,6 @@ int main() test_count_if_single_segment_seg2_single_inner(); test_count_if_single_segment_forward(); test_count_if_forward_multi_segment(); + test_count_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_count_test.cpp b/experimental/segmented_count_test.cpp index 60ed7da..7fa8092 100644 --- a/experimental/segmented_count_test.cpp +++ b/experimental/segmented_count_test.cpp @@ -257,6 +257,56 @@ void test_count_forward_multi_segment() BOOST_TEST_EQ(segmented_count(sv.begin(), test_detail::iter_at(sv, 7), 99), 0); } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.count] mandates "Exactly last - first applications of the corresponding +// predicate", so an element compared twice at a segment boundary is a +// conformance failure, not just a slowdown. There is no comparator overload, +// so the count is taken from the value type itself. +////////////////////////////////////////////////////////////////////////////// + +struct count_comparison_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const test_detail::counted_int target(3); + + test_detail::counted_int_ops().reset(); + segmented_count(c.begin(), test_detail::iter_at(c, n), target); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST_EQ(applied, n); + BOOST_TEST(spec != 0); + } +}; + +void test_count_comparison_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + // No match, every element a match, and a scattering of matches. + for(std::size_t i = 0; i != 16u; ++i) + vals[i] = int(i) + 100; + test_detail::for_each_shape_all + (vals, n, -999, count_comparison_check()); + + for(std::size_t i = 0; i != 16u; ++i) + vals[i] = 3; + test_detail::for_each_shape_all + (vals, n, -999, count_comparison_check()); + + for(std::size_t i = 0; i != 16u; ++i) + vals[i] = int(i) % 3 == 0 ? 3 : int(i) + 100; + test_detail::for_each_shape_all + (vals, n, -999, count_comparison_check()); + } +} + int main() { test_count_shape_matrix(); @@ -270,5 +320,6 @@ int main() test_count_single_segment_seg2_single_inner(); test_count_single_segment_forward(); test_count_forward_multi_segment(); + test_count_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_equal_test.cpp b/experimental/segmented_equal_test.cpp index 9f50c4a..e2b047e 100644 --- a/experimental/segmented_equal_test.cpp +++ b/experimental/segmented_equal_test.cpp @@ -607,6 +607,78 @@ void test_equal_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.equal] mandates "At most last1 - first1 applications of the +// corresponding predicate". The lower bound below is what stops the check +// from passing vacuously: the answer cannot be known before the first +// differing position has been looked at. +////////////////////////////////////////////////////////////////////////////// + +struct eq_int +{ + bool operator()(int a, int b) const { return a == b; } +}; + +struct equal_count_check +{ + // Index of the element of range 2 that was corrupted, or n1 for none. + std::size_t bad_pos; + + explicit equal_count_check(std::size_t p) : bad_pos(p) {} + + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + const std::size_t needed = bad_pos < n1 ? bad_pos + 1u : n1; + + { + test_detail::op_counter calls; + segmented_equal(c1.begin(), test_detail::iter_at(c1, n1), c2.begin(), + test_detail::counting_pred(calls, eq_int())); + BOOST_TEST(calls.n <= n1); + BOOST_TEST(calls.n >= needed); + } + { + test_detail::op_counter calls; + segmented_equal(c1.begin(), test_detail::make_sentinel(test_detail::iter_at(c1, n1)), + c2.begin(), test_detail::counting_pred(calls, eq_int())); + BOOST_TEST(calls.n <= n1); + BOOST_TEST(calls.n >= needed); + } + + BOOST_TEST(s1 != 0 && s2 != 0 && n2 != 0); + } +}; + +void test_equal_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 9u }; + + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n1 = sizes[s]; + const std::size_t n2 = n1 + 1u; + + int v1[10] = {}; + for(std::size_t i = 0; i != n1; ++i) + v1[i] = int(i) + 1; + + for(std::size_t bad = 0; bad <= n1; ++bad) { + int v2[11] = {}; + for(std::size_t i = 0; i != n1; ++i) + v2[i] = v1[i]; + v2[n1] = equal_shape_tail; + if(bad != n1) + v2[bad] = -7; + + test_detail::for_each_shape2_all + (v1, n1, v2, n2, -999, equal_count_check(bad)); + } + } +} + int main() { test_equal_shape_matrix(); @@ -639,5 +711,6 @@ int main() test_equal_single_segment_flat_first_range(); test_equal_single_segment_every_position(); + test_equal_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_fill_n_test.cpp b/experimental/segmented_fill_n_test.cpp index d3867db..cfa1761 100644 --- a/experimental/segmented_fill_n_test.cpp +++ b/experimental/segmented_fill_n_test.cpp @@ -125,11 +125,57 @@ void test_fill_n_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Assignment count. +// +// [alg.fill] mandates "Exactly n assignments", so a slot written twice at a +// segment boundary is a conformance failure. The count is taken from the +// value type, fill_n having no functor to instrument. +////////////////////////////////////////////////////////////////////////////// + +struct fill_n_assignment_check +{ + std::size_t offset; + std::size_t count; + + fill_n_assignment_check(std::size_t o, std::size_t k) : offset(o), count(k) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const test_detail::counted_int value(42); + + test_detail::counted_int_ops().reset(); + segmented_fill_n(test_detail::iter_at(c, offset), count, value); + const std::size_t applied = test_detail::counted_int_ops().assign; + + BOOST_TEST_EQ(applied, count); + BOOST_TEST(spec != 0 && n >= count); + } +}; + +void test_fill_n_assignment_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t offset = 0; offset <= n; ++offset) + for(std::size_t k = 0; offset + k <= n; ++k) + test_detail::for_each_shape_all + (vals, n, -999, fill_n_assignment_check(offset, k)); + } +} + int main() { test_fill_n_shape_matrix(); test_fill_n_partial(); test_fill_n_single_segment(); test_fill_n_non_segmented(); + test_fill_n_assignment_count(); return boost::report_errors(); } diff --git a/experimental/segmented_fill_test.cpp b/experimental/segmented_fill_test.cpp index 90e059e..b67e1c7 100644 --- a/experimental/segmented_fill_test.cpp +++ b/experimental/segmented_fill_test.cpp @@ -170,6 +170,42 @@ void test_fill_single_segment_sentinel() BOOST_TEST_EQ(*it, expected[i]); } +////////////////////////////////////////////////////////////////////////////// +// Assignment count. +// +// [alg.fill] mandates "Exactly last - first assignments", so a slot written +// twice at a segment boundary is a conformance failure. The count is taken +// from the value type, fill having no functor to instrument. +////////////////////////////////////////////////////////////////////////////// + +struct fill_assignment_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const test_detail::counted_int value(42); + + test_detail::counted_int_ops().reset(); + segmented_fill(c.begin(), test_detail::iter_at(c, n), value); + const std::size_t applied = test_detail::counted_int_ops().assign; + + BOOST_TEST_EQ(applied, n); + BOOST_TEST(spec != 0); + } +}; + +void test_fill_assignment_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) + test_detail::for_each_shape_all + (vals, sizes[s], -999, fill_assignment_check()); +} + int main() { test_fill_shape_matrix(); @@ -181,5 +217,6 @@ int main() test_fill_sentinel_non_segmented(); test_fill_seg2(); test_fill_single_segment_sentinel(); + test_fill_assignment_count(); return boost::report_errors(); } diff --git a/experimental/segmented_find_if_not_test.cpp b/experimental/segmented_find_if_not_test.cpp index cbaa616..2fb31d3 100644 --- a/experimental/segmented_find_if_not_test.cpp +++ b/experimental/segmented_find_if_not_test.cpp @@ -334,6 +334,66 @@ void test_find_if_not_forward_match_before_last_segment() BOOST_TEST(segmented_find_if_not(sv.begin(), last, not_equals_val(80)) == last); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.find] mandates "At most last - first applications of the corresponding +// predicate". The lower bound is not in the standard but follows from the +// answer: the result cannot be known without having applied pred to every +// element up to and including the one returned. Bracketing the count between +// the two catches an element retested when the scan crosses a segment +// boundary as well as a scan that stops short of the answer. +////////////////////////////////////////////////////////////////////////////// + +template +struct find_if_not_count_check +{ + Pred pred; + + explicit find_if_not_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const boost::container::vector flat = test_detail::flatten_n_ints(c, n); + std::size_t needed = n; + for(std::size_t i = 0; i != n; ++i) + if(!pred(flat[i])) { needed = i + 1u; break; } + + test_detail::op_counter calls; + segmented_find_if_not(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(calls.n >= needed); + BOOST_TEST(spec != 0); + } +}; + +template +void run_find_if_not_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, find_if_not_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, find_if_not_count_check(pred)); +} + +void test_find_if_not_predicate_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_find_if_not_count_shapes(vals, n, not_equals_val((v <= n) ? int(v) : -999)); + run_find_if_not_count_shapes(vals, n, not_multiple_of(1)); + run_find_if_not_count_shapes(vals, n, not_multiple_of(3)); + run_find_if_not_count_shapes(vals, n, is_positive()); + } +} + int main() { test_find_if_not_shape_matrix(); @@ -349,5 +409,6 @@ int main() test_find_if_not_single_segment_seg2_single_inner(); test_find_if_not_single_segment_forward(); test_find_if_not_forward_match_before_last_segment(); + test_find_if_not_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_find_if_test.cpp b/experimental/segmented_find_if_test.cpp index 3292654..0be30c8 100644 --- a/experimental/segmented_find_if_test.cpp +++ b/experimental/segmented_find_if_test.cpp @@ -331,6 +331,66 @@ void test_find_if_forward_match_before_last_segment() BOOST_TEST(segmented_find_if(sv.begin(), last, equals_val(80)) == last); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.find] mandates "At most last - first applications of the corresponding +// predicate". The lower bound is not in the standard but follows from the +// answer: the result cannot be known without having applied pred to every +// element up to and including the one returned. Bracketing the count between +// the two catches an element retested when the scan crosses a segment +// boundary as well as a scan that stops short of the answer. +////////////////////////////////////////////////////////////////////////////// + +template +struct find_if_count_check +{ + Pred pred; + + explicit find_if_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const boost::container::vector flat = test_detail::flatten_n_ints(c, n); + std::size_t needed = n; + for(std::size_t i = 0; i != n; ++i) + if(pred(flat[i])) { needed = i + 1u; break; } + + test_detail::op_counter calls; + segmented_find_if(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(calls.n >= needed); + BOOST_TEST(spec != 0); + } +}; + +template +void run_find_if_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, find_if_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, find_if_count_check(pred)); +} + +void test_find_if_predicate_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_find_if_count_shapes(vals, n, equals_val((v <= n) ? int(v) : -999)); + run_find_if_count_shapes(vals, n, multiple_of(1)); + run_find_if_count_shapes(vals, n, multiple_of(3)); + run_find_if_count_shapes(vals, n, is_negative()); + } +} + int main() { test_find_if_shape_matrix(); @@ -346,5 +406,6 @@ int main() test_find_if_single_segment_seg2_single_inner(); test_find_if_single_segment_forward(); test_find_if_forward_match_before_last_segment(); + test_find_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_find_last_if_not_test.cpp b/experimental/segmented_find_last_if_not_test.cpp index 9b971ff..de4b2e3 100644 --- a/experimental/segmented_find_last_if_not_test.cpp +++ b/experimental/segmented_find_last_if_not_test.cpp @@ -310,6 +310,65 @@ void test_find_last_if_not_forward_match_in_earlier_segment_seg2() BOOST_TEST(segmented_find_last_if_not(sv2.begin(), last, not_equals_val(6)) == last); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.find.last] mandates "At most last - first applications of the +// corresponding predicate", so neither the forward implementation, which has +// to walk the whole range, nor the bidirectional one, which scans backwards +// from the end, may retest an element at a segment boundary. +////////////////////////////////////////////////////////////////////////////// + +template +struct find_last_if_not_count_check +{ + Pred pred; + + explicit find_last_if_not_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_find_last_if_not(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(spec != 0); + } +}; + +template +void run_find_last_if_not_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, find_last_if_not_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, find_last_if_not_count_check(pred)); +} + +void test_find_last_if_not_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + for(int i = 0; i != 16; ++i) + vals[i] = i/2 + 1; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_find_last_if_not_count_shapes(vals, n, not_equals_val((v <= n) ? int(v) : -999)); + + for(std::size_t p = 0; p != n; ++p) { + for(std::size_t i = 0; i != 16u; ++i) + vals[i] = (i == p) ? -int(i + 1u) : int(i + 1u); + run_find_last_if_not_count_shapes(vals, n, is_positive()); + } + + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + run_find_last_if_not_count_shapes(vals, n, is_positive()); + } +} + int main() { test_find_last_if_not_shape_matrix(); @@ -325,5 +384,6 @@ int main() test_find_last_if_not_single_segment_sentinel(); test_find_last_if_not_forward_match_in_earlier_segment(); test_find_last_if_not_forward_match_in_earlier_segment_seg2(); + test_find_last_if_not_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_find_last_if_test.cpp b/experimental/segmented_find_last_if_test.cpp index 1c2e3af..7472bdf 100644 --- a/experimental/segmented_find_last_if_test.cpp +++ b/experimental/segmented_find_last_if_test.cpp @@ -310,6 +310,65 @@ void test_find_last_if_forward_match_in_earlier_segment_seg2() BOOST_TEST(segmented_find_last_if(sv2.begin(), last, equals_val(6)) == last); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.find.last] mandates "At most last - first applications of the +// corresponding predicate", so neither the forward implementation, which has +// to walk the whole range, nor the bidirectional one, which scans backwards +// from the end, may retest an element at a segment boundary. +////////////////////////////////////////////////////////////////////////////// + +template +struct find_last_if_count_check +{ + Pred pred; + + explicit find_last_if_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_find_last_if(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(spec != 0); + } +}; + +template +void run_find_last_if_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, find_last_if_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, find_last_if_count_check(pred)); +} + +void test_find_last_if_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + for(int i = 0; i != 16; ++i) + vals[i] = i/2 + 1; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_find_last_if_count_shapes(vals, n, equals_val((v <= n) ? int(v) : -999)); + + for(std::size_t p = 0; p != n; ++p) { + for(std::size_t i = 0; i != 16u; ++i) + vals[i] = (i == p) ? -int(i + 1u) : int(i + 1u); + run_find_last_if_count_shapes(vals, n, is_negative()); + } + + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + run_find_last_if_count_shapes(vals, n, is_negative()); + } +} + int main() { test_find_last_if_shape_matrix(); @@ -325,5 +384,6 @@ int main() test_find_last_if_single_segment_sentinel(); test_find_last_if_forward_match_in_earlier_segment(); test_find_last_if_forward_match_in_earlier_segment_seg2(); + test_find_last_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_find_last_test.cpp b/experimental/segmented_find_last_test.cpp index 4be81dd..7da19d2 100644 --- a/experimental/segmented_find_last_test.cpp +++ b/experimental/segmented_find_last_test.cpp @@ -277,6 +277,53 @@ void test_find_last_forward_match_in_earlier_segment_seg2() BOOST_TEST(segmented_find_last(sv2.begin(), last, 6) == last); } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.find.last] mandates "At most last - first applications of the +// corresponding predicate and projection", which holds for the backward scan +// a bidirectional range allows as much as for the forward one a forward range +// forces. There is no predicate overload, so the count comes from the value +// type. +////////////////////////////////////////////////////////////////////////////// + +struct find_last_comparison_check +{ + int value; + + explicit find_last_comparison_check(int v) : value(v) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::counted_int_ops().reset(); + segmented_find_last(c.begin(), test_detail::iter_at(c, n), test_detail::counted_int(value)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST(applied <= n); + BOOST_TEST(spec != 0); + } +}; + +void test_find_last_comparison_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i/2 + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) { + const int target = (v <= n) ? int(v) : -999; + test_detail::for_each_shape_all + (vals, n, -999, find_last_comparison_check(target)); + test_detail::for_each_shape_all_fwd + (vals, n, -999, find_last_comparison_check(target)); + } + } +} + int main() { test_find_last_shape_matrix(); @@ -292,5 +339,6 @@ int main() test_find_last_single_segment_sentinel(); test_find_last_forward_match_in_earlier_segment(); test_find_last_forward_match_in_earlier_segment_seg2(); + test_find_last_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_find_test.cpp b/experimental/segmented_find_test.cpp index 51083d8..1e9fbc9 100644 --- a/experimental/segmented_find_test.cpp +++ b/experimental/segmented_find_test.cpp @@ -406,6 +406,57 @@ void test_find_forward_match_before_last_segment() BOOST_TEST(segmented_find(sv.begin(), last, 80) == last); } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.find] mandates "At most last - first applications of the corresponding +// predicate". The lower bound is what stops the check from passing vacuously: +// the answer cannot be known before the element at it has been compared. +// There is no predicate overload, so the count comes from the value type. +////////////////////////////////////////////////////////////////////////////// + +struct find_comparison_check +{ + int value; + + explicit find_comparison_check(int v) : value(v) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const boost::container::vector flat = test_detail::flatten_n_ints(c, n); + std::size_t expected = flat.size(); + for(std::size_t i = 0; i != flat.size(); ++i) { + if(flat[i] == value) { expected = i; break; } + } + + test_detail::counted_int_ops().reset(); + segmented_find(c.begin(), test_detail::iter_at(c, n), test_detail::counted_int(value)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST(applied <= n); + BOOST_TEST(applied >= (expected < n ? expected + 1u : n)); + BOOST_TEST(spec != 0); + } +}; + +void test_find_comparison_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) { + const int target = (v <= n) ? int(v) : -999; + test_detail::for_each_shape_all + (vals, n, -999, find_comparison_check(target)); + } + } +} + int main() { test_find_shape_matrix(); @@ -427,5 +478,6 @@ int main() test_find_single_segment_seg2_single_inner(); test_find_single_segment_forward(); test_find_forward_match_before_last_segment(); + test_find_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_for_each_test.cpp b/experimental/segmented_for_each_test.cpp index 71c75f3..dca2af9 100644 --- a/experimental/segmented_for_each_test.cpp +++ b/experimental/segmented_for_each_test.cpp @@ -176,6 +176,40 @@ void test_for_each_single_segment_sentinel() BOOST_TEST_EQ(*it, expected[i]); } +////////////////////////////////////////////////////////////////////////////// +// Function application count. +// +// [alg.foreach] mandates "Applies f exactly last - first times", so a segment +// walked twice or one skipped is caught here even in the cases where the +// accumulated answer still comes out right. +////////////////////////////////////////////////////////////////////////////// + +struct for_each_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_for_each(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_fun(calls, doubler())); + + BOOST_TEST_EQ(calls.n, n); + BOOST_TEST(test_detail::filler_intact(c, n, -999)); + BOOST_TEST(spec != 0); + } +}; + +void test_for_each_application_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) + test_detail::for_each_shape_all(vals, sizes[s], -999, for_each_count_check()); +} + int main() { test_for_each_shape_matrix(); @@ -186,5 +220,6 @@ int main() test_for_each_sentinel_non_segmented(); test_for_each_seg2(); test_for_each_single_segment_sentinel(); + test_for_each_application_count(); return boost::report_errors(); } diff --git a/experimental/segmented_is_partitioned_test.cpp b/experimental/segmented_is_partitioned_test.cpp index 0162c1b..303af28 100644 --- a/experimental/segmented_is_partitioned_test.cpp +++ b/experimental/segmented_is_partitioned_test.cpp @@ -304,6 +304,66 @@ void test_is_partitioned_forward_violation_before_last_segment() BOOST_TEST(!segmented_is_partitioned(sv.begin(), test_detail::iter_at(sv, 7), less_than_5())); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.partitions] mandates "At most last - first applications of pred" for +// is_partitioned, so the two scans it is specified as -- the satisfying prefix +// and the non-satisfying remainder -- must between them touch each element at +// most once, whatever segment boundary they meet in between. +////////////////////////////////////////////////////////////////////////////// + +struct is_partitioned_count_check +{ + int threshold; + + explicit is_partitioned_count_check(int t) : threshold(t) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_is_partitioned(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, less_than_threshold(threshold))); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(spec != 0); + } +}; + +void run_is_partitioned_count_shapes(const int* vals, std::size_t n, int threshold) +{ + test_detail::for_each_shape_all(vals, n, -999, is_partitioned_count_check(threshold)); + test_detail::for_each_shape_all_fwd(vals, n, -999, is_partitioned_count_check(threshold)); +} + +void test_is_partitioned_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + std::size_t i = 0; + int vals[16] = { 0 }; + + // Properly partitioned with the boundary at every position in turn, so + // the second scan starts in the middle of a segment as well as at one of + // its ends. + for(std::size_t p = 0; p <= n; ++p) { + for(i = 0; i != n; ++i) + vals[i] = (i < p) ? int(i) : 1000 + int(i); + run_is_partitioned_count_shapes(vals, n, 500); + } + + // Not partitioned: a satisfying element after a non-satisfying one, at + // every position in turn, so the early exit lands everywhere too. + for(std::size_t p = 1; p < n; ++p) { + for(i = 0; i != n; ++i) + vals[i] = (i == p) ? int(i) : 1000 + int(i); + run_is_partitioned_count_shapes(vals, n, 500); + } + } +} + int main() { test_is_partitioned_shape_matrix(); @@ -319,5 +379,6 @@ int main() test_is_partitioned_single_segment_seg2_single_inner(); test_is_partitioned_single_segment_forward(); test_is_partitioned_forward_violation_before_last_segment(); + test_is_partitioned_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_is_sorted_test.cpp b/experimental/segmented_is_sorted_test.cpp index e9d464f..4766bda 100644 --- a/experimental/segmented_is_sorted_test.cpp +++ b/experimental/segmented_is_sorted_test.cpp @@ -211,6 +211,53 @@ void test_is_sorted_single_segment_sentinel() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.sorted] states the complexity of is_sorted/is_sorted_until as "Linear", +// so what is pinned here is the constant: a scan comparing each adjacent pair +// once needs last - first - 1 comparisons, and anything above last - first +// means a pair compared twice, which is what a segment boundary invites. +////////////////////////////////////////////////////////////////////////////// + +struct is_sorted_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_is_sorted(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, greater_comp())); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(spec != 0); + } +}; + +void test_is_sorted_comparison_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + //Descending, so the range is sorted under greater_comp and the scan has + //to run to the end. + for(int i = 0; i != 16; ++i) + vals[i] = (16 - i) * 10; + test_detail::for_each_shape_all(vals, n, 99999, is_sorted_count_check()); + + //One inversion at each adjacent pair in turn, so the early exit lands at + //every position, inside a segment and at a boundary. + for(std::size_t p = 1u; p < n; ++p) { + for(int i = 0; i != 16; ++i) + vals[i] = (16 - i) * 10; + vals[p] = vals[p - 1u] + 1; + test_detail::for_each_shape_all(vals, n, 99999, is_sorted_count_check()); + } + } +} + int main() { test_is_sorted_shape_matrix(); @@ -222,5 +269,6 @@ int main() test_is_sorted_sentinel_non_segmented(); test_is_sorted_seg2(); test_is_sorted_single_segment_sentinel(); + test_is_sorted_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_is_sorted_until_test.cpp b/experimental/segmented_is_sorted_until_test.cpp index eb7034f..27e8c1e 100644 --- a/experimental/segmented_is_sorted_until_test.cpp +++ b/experimental/segmented_is_sorted_until_test.cpp @@ -236,6 +236,53 @@ void test_is_sorted_until_single_segment_sentinel() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.sorted] states the complexity of is_sorted_until as "Linear", so what +// is pinned here is the constant: a scan comparing each adjacent pair once +// needs last - first - 1 comparisons, and anything above last - first means a +// pair compared twice, which is what a segment boundary invites. +////////////////////////////////////////////////////////////////////////////// + +struct is_sorted_until_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_is_sorted_until(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, greater_comp())); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(spec != 0); + } +}; + +void test_is_sorted_until_comparison_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + //Descending, so the range is sorted under greater_comp and the scan has + //to run to the end. + for(int i = 0; i != 16; ++i) + vals[i] = (16 - i) * 10; + test_detail::for_each_shape_all(vals, n, 99999, is_sorted_until_count_check()); + + //One inversion at each adjacent pair in turn, so the early exit lands at + //every position, inside a segment and at a boundary. + for(std::size_t p = 1u; p < n; ++p) { + for(int i = 0; i != 16; ++i) + vals[i] = (16 - i) * 10; + vals[p] = vals[p - 1u] + 1; + test_detail::for_each_shape_all(vals, n, 99999, is_sorted_until_count_check()); + } + } +} + int main() { test_is_sorted_until_shape_matrix(); @@ -246,5 +293,6 @@ int main() test_is_sorted_until_every_position(); test_is_sorted_until_every_position_seg2(); test_is_sorted_until_single_segment_sentinel(); + test_is_sorted_until_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_merge_test.cpp b/experimental/segmented_merge_test.cpp index d887f1e..6a64097 100644 --- a/experimental/segmented_merge_test.cpp +++ b/experimental/segmented_merge_test.cpp @@ -990,6 +990,69 @@ void test_merge_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.merge] mandates "At most N - 1 comparisons", N being the total input +// length. That is far tighter than the set operations allow, so a single +// element re-compared at a destination boundary already breaks it. +////////////////////////////////////////////////////////////////////////////// + +struct less_comp +{ + bool operator()(int a, int b) const { return a < b; } +}; + +inline std::size_t merge_comparison_bound(std::size_t n1, std::size_t n2) +{ + const std::size_t total = n1 + n2; + return total ? total - 1u : 0u; +} + +struct merge_count_check +{ + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + boost::container::vector flat(n1 + n2 + 1u, -1); + { + test_detail::op_counter calls; + segmented_merge(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + flat.begin(), test_detail::counting_pred(calls, less_comp())); + BOOST_TEST(calls.n <= merge_comparison_bound(n1, n2)); + } + + for(std::size_t block = 1u; block <= 3u; ++block) { + test_detail::seg_vector out; + for(std::size_t room = 0; room <= n1 + n2; room += block) + out.add_segment(block, -1); + + test_detail::op_counter calls; + segmented_merge(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + out.begin(), test_detail::counting_pred(calls, less_comp())); + BOOST_TEST(calls.n <= merge_comparison_bound(n1, n2)); + } + + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void test_merge_comparison_count() +{ + const int v1[] = {1, 2, 2, 3, 5, 8}; + const int v2[] = {2, 3, 3, 4, 8, 9}; + + static const std::size_t pairs[][2] = + { {0u, 0u}, {0u, 3u}, {3u, 0u}, {1u, 1u}, {2u, 4u}, {4u, 2u}, {5u, 6u} }; + + for(std::size_t p = 0; p != sizeof(pairs)/sizeof(pairs[0]); ++p) + test_detail::for_each_shape2_all + (v1, pairs[p][0], v2, pairs[p][1], -999, merge_count_check()); +} + int main() { test_merge_shape_matrix(); @@ -1031,5 +1094,6 @@ int main() test_merge_single_segment_sentinel(); test_merge_single_segment_with_comp(); + test_merge_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_mismatch_test.cpp b/experimental/segmented_mismatch_test.cpp index b5fc005..11995e8 100644 --- a/experimental/segmented_mismatch_test.cpp +++ b/experimental/segmented_mismatch_test.cpp @@ -1393,6 +1393,92 @@ void test_mismatch_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [mismatch] mandates "At most last1 - first1 applications of the +// corresponding predicate", and min(last1 - first1, last2 - first2) for the +// four-iterator form. The lower bound below is what stops the check from +// passing vacuously: the returned position cannot be known before the element +// at it has been looked at. +////////////////////////////////////////////////////////////////////////////// + +struct mismatch_eq_int +{ + bool operator()(int a, int b) const { return a == b; } +}; + +struct mismatch_count_check +{ + // Index of the element of range 2 that was corrupted, or n1 for none. + std::size_t bad_pos; + + explicit mismatch_count_check(std::size_t p) : bad_pos(p) {} + + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + const std::size_t k = bad_pos < n1 ? bad_pos : n1; + + { + test_detail::op_counter calls; + segmented_mismatch(c1.begin(), test_detail::iter_at(c1, n1), c2.begin(), + test_detail::counting_pred(calls, mismatch_eq_int())); + BOOST_TEST(calls.n <= n1); + BOOST_TEST(calls.n >= (k < n1 ? k + 1u : n1)); + } + { + test_detail::op_counter calls; + segmented_mismatch(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + test_detail::counting_pred(calls, mismatch_eq_int())); + BOOST_TEST(calls.n <= (n1 < n2 ? n1 : n2)); + BOOST_TEST(calls.n >= (k < n1 ? k + 1u : n1)); + } + { + // Second range bounded short, so the walk can also stop on it. + const std::size_t half = n1 / 2u; + const std::size_t k_half = k < half ? k : half; + + test_detail::op_counter calls; + segmented_mismatch(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, half), + test_detail::counting_pred(calls, mismatch_eq_int())); + BOOST_TEST(calls.n <= half); + BOOST_TEST(calls.n >= (k_half < half ? k_half + 1u : half)); + } + + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void test_mismatch_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 9u }; + + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n1 = sizes[s]; + const std::size_t n2 = n1 + 1u; + + int v1[10] = {}; + for(std::size_t i = 0; i != n1; ++i) + v1[i] = int(i) + 1; + + for(std::size_t bad = 0; bad <= n1; ++bad) { + int v2[11] = {}; + for(std::size_t i = 0; i != n1; ++i) + v2[i] = v1[i]; + v2[n1] = mismatch_shape_tail; + if(bad != n1) + v2[bad] = -7; + + test_detail::for_each_shape2_all + (v1, n1, v2, n2, -999, mismatch_count_check(bad)); + } + } +} + int main() { test_mismatch_shape_matrix(); @@ -1456,5 +1542,6 @@ int main() test_mismatch_2r_single_segment_second_range_seg2(); test_mismatch_2r_single_segment_every_position(); + test_mismatch_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_none_of_test.cpp b/experimental/segmented_none_of_test.cpp index 2ff1e4f..b4bc341 100644 --- a/experimental/segmented_none_of_test.cpp +++ b/experimental/segmented_none_of_test.cpp @@ -272,6 +272,66 @@ void test_none_of_forward_hit_before_last_segment() BOOST_TEST(segmented_none_of(sv.begin(), last, equals_val(80))); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.none.of] mandates "At most last - first applications of the predicate". +// The lower bound is not in the standard but follows from the answer: false +// cannot be returned without having applied pred to every element up to and +// including the first hit, and true cannot be returned without having applied +// it to all of them. Bracketing the count catches an element retested when +// the scan crosses a segment boundary. +////////////////////////////////////////////////////////////////////////////// + +template +struct none_of_count_check +{ + Pred pred; + + explicit none_of_count_check(Pred p) : pred(p) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + const boost::container::vector flat = test_detail::flatten_n_ints(c, n); + std::size_t needed = n; + for(std::size_t i = 0; i != n; ++i) + if(pred(flat[i])) { needed = i + 1u; break; } + + test_detail::op_counter calls; + segmented_none_of(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, pred)); + + BOOST_TEST(calls.n <= n); + BOOST_TEST(calls.n >= needed); + BOOST_TEST(spec != 0); + } +}; + +template +void run_none_of_count_shapes(const int* vals, std::size_t n, Pred pred) +{ + test_detail::for_each_shape_all(vals, n, -999, none_of_count_check(pred)); + test_detail::for_each_shape_all_fwd(vals, n, -999, none_of_count_check(pred)); +} + +void test_none_of_predicate_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n + 1u; ++v) + run_none_of_count_shapes(vals, n, equals_val((v <= n) ? int(v) : -999)); + run_none_of_count_shapes(vals, n, multiple_of(1)); + run_none_of_count_shapes(vals, n, multiple_of(3)); + run_none_of_count_shapes(vals, n, is_negative()); + } +} + int main() { test_none_of_shape_matrix(); @@ -285,5 +345,6 @@ int main() test_none_of_single_segment_seg2_single_inner(); test_none_of_single_segment_forward(); test_none_of_forward_hit_before_last_segment(); + test_none_of_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_partition_copy_test.cpp b/experimental/segmented_partition_copy_test.cpp index f7cf3fa..e0e207d 100644 --- a/experimental/segmented_partition_copy_test.cpp +++ b/experimental/segmented_partition_copy_test.cpp @@ -630,6 +630,99 @@ void test_partition_copy_unrolled_blocks() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.partitions] mandates "Exactly last - first applications of pred" for +// partition_copy, whatever the segmentation of the source and of either +// output. A leaf that applies pred, then finds one of the two destination +// segments full and returns without consuming the element it has just tested +// hands that element back to the walker, which re-applies pred to it. +////////////////////////////////////////////////////////////////////////////// + +struct partition_copy_count_check +{ + template + void operator()(CSrc& src, std::size_t n_src, const char* src_spec, + CTrue& tdst, std::size_t n_t, const char* t_spec, + CFalse& fdst, std::size_t n_f, const char* f_spec) const + { + test_detail::op_counter calls; + segmented_partition_copy(src.begin(), test_detail::iter_at(src, n_src), + tdst.begin(), fdst.begin(), + test_detail::counting_pred(calls, is_even())); + + BOOST_TEST_EQ(calls.n, n_src); + BOOST_TEST(test_detail::filler_intact(tdst, n_t, shape_filler)); + BOOST_TEST(test_detail::filler_intact(fdst, n_f, shape_filler)); + BOOST_TEST(src_spec != 0 && t_spec != 0 && f_spec != 0); + } +}; + +void partition_copy_add_block(test_detail::seg_vector& c, std::size_t block) +{ c.add_segment(block, 0); } + +void partition_copy_add_block(test_detail::seg2_vector& c, std::size_t block) +{ + test_detail::seg_vector inner; + inner.add_segment(block, 0); + c.add_segment(inner); +} + +// Output segments of a handful of elements each, so that boundary crossings +// dominate: both outputs cross, and the depth-2 form multiplies the crossings. +template +void partition_copy_count_small_dst(std::size_t n, std::size_t block) +{ + boost::container::vector src; + src.reserve(n); + for(std::size_t i = 0; i != n; ++i) + src.push_back(static_cast(i) + 1); + + CDst tdst, fdst; + for(std::size_t room = 0; room < n; room += block) { + partition_copy_add_block(tdst, block); + partition_copy_add_block(fdst, block); + } + + test_detail::op_counter calls; + segmented_partition_copy(src.begin(), src.end(), tdst.begin(), fdst.begin(), + test_detail::counting_pred(calls, is_even())); + BOOST_TEST_EQ(calls.n, n); +} + +void test_partition_copy_predicate_count() +{ + static const int mixed[] = {1, 2, 3, 4, 5, 6, 7, 8}; + static const int t_only[] = {2, 4, 6, 8, 10, 12}; + static const int f_only[] = {1, 3, 5, 7, 9, 11}; + static const int* const sets[] = {mixed, t_only, f_only}; + static const std::size_t set_len[] = {8u, 6u, 6u}; + static const std::size_t sizes[] = {0u, 1u, 2u, 5u}; + + for(std::size_t s = 0; s != sizeof(sets)/sizeof(sets[0]); ++s) { + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) { + const std::size_t n = sizes[i]; + if(n > set_len[s]) + continue; + const std::size_t nt = shape_true_count(sets[s], n); + const std::size_t nf = n - nt; + test_detail::for_each_shape3_all + (sets[s], n, shape_dst_vals, nt, shape_dst_vals, nf, + shape_filler, partition_copy_count_check()); + test_detail::for_each_shape3_all + (sets[s], n, shape_dst_vals, nt + 2u, shape_dst_vals, nf + 2u, + shape_filler, partition_copy_count_check()); + } + } + + static const std::size_t blocks[] = {1u, 2u, 3u, 8u, 16u}; + for(std::size_t b = 0; b != sizeof(blocks)/sizeof(blocks[0]); ++b) { + partition_copy_count_small_dst >(64u, blocks[b]); + partition_copy_count_small_dst >(64u, blocks[b]); + } +} + int main() { test_partition_copy_segmented(); @@ -651,5 +744,6 @@ int main() test_partition_copy_single_segment_outs_from_flat(); test_partition_copy_shape_matrix(); test_partition_copy_unrolled_blocks(); + test_partition_copy_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_partition_test.cpp b/experimental/segmented_partition_test.cpp index a7f1472..2ec8fa6 100644 --- a/experimental/segmented_partition_test.cpp +++ b/experimental/segmented_partition_test.cpp @@ -343,6 +343,54 @@ void test_partition_forward_matches_in_earlier_segment() BOOST_TEST_EQ(*test_detail::iter_at(sv, 6), 99); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.partitions] mandates that partition performs "Exactly N applications of +// the predicate", N = last - first, whichever iterator category is used, so +// neither the two-ended bidirectional scan nor the forward one may retest an +// element when it crosses a segment boundary. +////////////////////////////////////////////////////////////////////////////// + +struct partition_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_partition(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, is_even())); + + BOOST_TEST_EQ(calls.n, n); + BOOST_TEST(test_detail::filler_intact(c, n, -999)); + BOOST_TEST(spec != 0); + } +}; + +void test_partition_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + run_partition_shapes(vals, n, partition_count_check()); + + for(int i = 0; i != 16; ++i) + vals[i] = 2*i + 2; + run_partition_shapes(vals, n, partition_count_check()); + for(int i = 0; i != 16; ++i) + vals[i] = 2*i + 1; + run_partition_shapes(vals, n, partition_count_check()); + + for(int i = 0; i != 16; ++i) + vals[i] = (i < 8) ? 2*i + 1 : 2*i + 2; + run_partition_shapes(vals, n, partition_count_check()); + } +} + int main() { test_partition_shape_matrix(); @@ -356,5 +404,6 @@ int main() test_partition_movable_seg2(); test_partition_single_segment_sentinel(); test_partition_forward_matches_in_earlier_segment(); + test_partition_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_remove_copy_if_test.cpp b/experimental/segmented_remove_copy_if_test.cpp index a0e7fe4..d9f9d79 100644 --- a/experimental/segmented_remove_copy_if_test.cpp +++ b/experimental/segmented_remove_copy_if_test.cpp @@ -492,6 +492,95 @@ void test_remove_copy_if_unrolled_blocks() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.remove] mandates "Exactly last - first applications of the corresponding +// predicate", whatever the segmentation of either range. A leaf that applies +// pred, then finds the destination segment full and returns without consuming +// the element it has just tested hands that element back to the destination +// walker, which re-applies pred to it; only an equality catches that. +////////////////////////////////////////////////////////////////////////////// + +struct remove_copy_if_count_check +{ + template + void operator()(CSrc& src, std::size_t n_src, const char* src_spec, + CDst& dst, std::size_t n_dst, const char* dst_spec) const + { + test_detail::op_counter calls; + segmented_remove_copy_if(src.begin(), test_detail::iter_at(src, n_src), dst.begin(), + test_detail::counting_pred(calls, is_even())); + + BOOST_TEST_EQ(calls.n, n_src); + BOOST_TEST(test_detail::filler_intact(dst, n_dst, shape_filler)); + BOOST_TEST(src_spec != 0 && dst_spec != 0); + } +}; + +void remove_copy_if_add_block(test_detail::seg_vector& c, std::size_t block) +{ c.add_segment(block, 0); } + +void remove_copy_if_add_block(test_detail::seg2_vector& c, std::size_t block) +{ + test_detail::seg_vector inner; + inner.add_segment(block, 0); + c.add_segment(inner); +} + +// Destination segments of a handful of elements each, so that boundary +// crossings dominate: with 64 elements and blocks of 8 there are seven of them, +// and the depth-2 destination multiplies them. +template +void remove_copy_if_count_small_dst(std::size_t n, std::size_t block, bool keep_all) +{ + boost::container::vector src; + src.reserve(n); + for(std::size_t i = 0; i != n; ++i) + src.push_back(keep_all ? 2*static_cast(i) + 1 : 2*static_cast(i) + 2); + + CDst dst; + for(std::size_t room = 0; room < n; room += block) + remove_copy_if_add_block(dst, block); + + test_detail::op_counter calls; + segmented_remove_copy_if(src.begin(), src.end(), dst.begin(), + test_detail::counting_pred(calls, is_even())); + BOOST_TEST_EQ(calls.n, n); +} + +void test_remove_copy_if_predicate_count() +{ + static const int mixed[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + static const int keep[] = {1, 3, 5, 7, 9, 11}; + static const int drop[] = {2, 4, 6, 8, 10, 12}; + static const int* const sets[] = {mixed, keep, drop}; + static const std::size_t set_len[] = {10u, 6u, 6u}; + static const std::size_t sizes[] = {0u, 1u, 2u, 3u, 5u, 6u}; + + for(std::size_t s = 0; s != sizeof(sets)/sizeof(sets[0]); ++s) { + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) { + const std::size_t n_src = sizes[i]; + if(n_src > set_len[s]) + continue; + const std::size_t m = shape_survivor_count(sets[s], n_src); + test_detail::for_each_shape2_all + (sets[s], n_src, shape_dst_vals, m, shape_filler, remove_copy_if_count_check()); + test_detail::for_each_shape2_all + (sets[s], n_src, shape_dst_vals, m + 3u, shape_filler, remove_copy_if_count_check()); + } + } + + static const std::size_t blocks[] = {1u, 2u, 3u, 8u, 16u}; + for(std::size_t b = 0; b != sizeof(blocks)/sizeof(blocks[0]); ++b) { + for(int ka = 0; ka != 2; ++ka) { + const bool k = ka != 0; + remove_copy_if_count_small_dst >(64u, blocks[b], k); + remove_copy_if_count_small_dst >(64u, blocks[b], k); + } + } +} + int main() { test_remove_copy_if_segmented(); @@ -514,5 +603,6 @@ int main() test_remove_copy_if_single_segment_dst_from_flat(); test_remove_copy_if_shape_matrix(); test_remove_copy_if_unrolled_blocks(); + test_remove_copy_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_remove_copy_test.cpp b/experimental/segmented_remove_copy_test.cpp index 8d009e7..514ea2f 100644 --- a/experimental/segmented_remove_copy_test.cpp +++ b/experimental/segmented_remove_copy_test.cpp @@ -463,6 +463,100 @@ void test_remove_copy_unrolled_blocks() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.remove] mandates "Exactly last - first applications of the +// corresponding predicate or comparison", whatever the segmentation of either +// range. A leaf that compares an element, then finds the destination segment +// full and returns without consuming it hands that element back to the +// destination walker, which compares it again; only an equality catches that. +// There is no predicate overload, so the count comes from the value type. +////////////////////////////////////////////////////////////////////////////// + +struct remove_copy_count_check +{ + template + void operator()(CSrc& src, std::size_t n_src, const char* src_spec, + CDst& dst, std::size_t n_dst, const char* dst_spec) const + { + test_detail::counted_int_ops().reset(); + segmented_remove_copy(src.begin(), test_detail::iter_at(src, n_src), dst.begin(), + test_detail::counted_int(shape_removed)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST_EQ(applied, n_src); + BOOST_TEST(src_spec != 0 && dst_spec != 0 && n_dst <= n_src + 3u); + } +}; + +void remove_copy_add_block(test_detail::seg_vector& c, std::size_t block) +{ c.add_segment(block, test_detail::counted_int(0)); } + +void remove_copy_add_block(test_detail::seg2_vector& c, std::size_t block) +{ + test_detail::seg_vector inner; + inner.add_segment(block, test_detail::counted_int(0)); + c.add_segment(inner); +} + +// Destination segments of a handful of elements each, so that boundary +// crossings dominate: with 64 elements and blocks of 8 there are seven of them, +// and the depth-2 destination multiplies them. +template +void remove_copy_count_small_dst(std::size_t n, std::size_t block, bool all_kept) +{ + boost::container::vector src; + src.reserve(n); + for(std::size_t i = 0; i != n; ++i) + src.push_back(test_detail::counted_int + (all_kept ? static_cast(i) + 10 : shape_removed)); + + CDst dst; + for(std::size_t room = 0; room < n; room += block) + remove_copy_add_block(dst, block); + + test_detail::counted_int_ops().reset(); + segmented_remove_copy(src.begin(), src.end(), dst.begin(), + test_detail::counted_int(shape_removed)); + BOOST_TEST_EQ(test_detail::counted_int_ops().cmp, n); +} + +void test_remove_copy_comparison_count() +{ + static const int mixed[] = {1, 2, 3, 2, 4, 2, 5, 6, 2, 7}; + static const int edges[] = {2, 1, 3, 4, 5, 2}; + static const int none[] = {1, 3, 5, 7, 9, 11}; + static const int all_rm[] = {2, 2, 2, 2, 2, 2}; + static const int* const sets[] = {mixed, edges, none, all_rm}; + static const std::size_t set_len[] = {10u, 6u, 6u, 6u}; + static const std::size_t sizes[] = {0u, 1u, 2u, 3u, 5u, 6u}; + + for(std::size_t s = 0; s != sizeof(sets)/sizeof(sets[0]); ++s) { + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) { + const std::size_t n_src = sizes[i]; + if(n_src > set_len[s]) + continue; + const std::size_t m = shape_survivor_count(sets[s], n_src); + test_detail::for_each_shape2_all + (sets[s], n_src, shape_dst_vals, m, shape_filler, remove_copy_count_check()); + test_detail::for_each_shape2_all + (sets[s], n_src, shape_dst_vals, m + 3u, shape_filler, remove_copy_count_check()); + } + } + + static const std::size_t blocks[] = {1u, 2u, 3u, 8u, 16u}; + for(std::size_t b = 0; b != sizeof(blocks)/sizeof(blocks[0]); ++b) { + for(int ak = 0; ak != 2; ++ak) { + const bool a = ak != 0; + remove_copy_count_small_dst > + (64u, blocks[b], a); + remove_copy_count_small_dst > + (64u, blocks[b], a); + } + } +} + int main() { test_remove_copy_segmented(); @@ -483,5 +577,6 @@ int main() test_remove_copy_single_segment_dst_from_flat(); test_remove_copy_shape_matrix(); test_remove_copy_unrolled_blocks(); + test_remove_copy_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_remove_if_test.cpp b/experimental/segmented_remove_if_test.cpp index 781805b..556fa1b 100644 --- a/experimental/segmented_remove_if_test.cpp +++ b/experimental/segmented_remove_if_test.cpp @@ -406,6 +406,55 @@ void test_remove_if_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.remove] mandates "Exactly last - first applications of the corresponding +// predicate". segmented_remove_if reaches that total in two pieces, the +// find_if that locates the first match and the compacting pass over the rest, +// so an element tested by both, or one re-tested when the write pointer crosses +// a segment boundary, shows up as a surplus here. +////////////////////////////////////////////////////////////////////////////// + +template +struct remove_if_count_check +{ + template + void operator()(C& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_remove_if(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, Pred())); + + BOOST_TEST_EQ(calls.n, n); + BOOST_TEST(test_detail::filler_intact(c, n, shape_filler)); + BOOST_TEST(spec != 0); + } +}; + +void test_remove_if_predicate_count() +{ + static const int mixed[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + static const int edges[] = {2, 1, 3, 5, 7, 4}; + static const int odds[] = {1, 3, 5, 7, 9, 11}; + static const int evens[] = {2, 4, 6, 8, 10, 12}; + static const int* const sets[] = {mixed, edges, odds, evens}; + static const std::size_t set_len[] = {10u, 6u, 6u, 6u}; + static const std::size_t sizes[] = {0u, 1u, 2u, 3u, 5u, 6u, 10u}; + + for(std::size_t s = 0; s != sizeof(sets)/sizeof(sets[0]); ++s) { + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) { + const std::size_t n = sizes[i]; + if(n > set_len[s]) + continue; + test_detail::for_each_shape_all + (sets[s], n, shape_filler, remove_if_count_check()); + test_detail::for_each_shape_all + (sets[s], n, shape_filler, remove_if_count_check()); + } + } +} + int main() { test_remove_if_segmented(); @@ -428,5 +477,6 @@ int main() test_remove_if_movable_seg(); test_remove_if_movable_seg2(); test_remove_if_shape_matrix(); + test_remove_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_remove_test.cpp b/experimental/segmented_remove_test.cpp index f71cddc..9b10cae 100644 --- a/experimental/segmented_remove_test.cpp +++ b/experimental/segmented_remove_test.cpp @@ -386,6 +386,52 @@ void test_remove_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.remove] mandates "Exactly last - first applications of the +// corresponding predicate or comparison", so an element compared twice at a +// segment boundary is a conformance failure. segmented_remove is built from +// segmented_find plus segmented_remove_copy, and the count is what catches +// the join between them re-testing the element find stopped on. +////////////////////////////////////////////////////////////////////////////// + +struct remove_comparison_check +{ + template + void operator()(C& c, std::size_t n, const char* spec) const + { + test_detail::counted_int_ops().reset(); + segmented_remove(c.begin(), test_detail::iter_at(c, n), + test_detail::counted_int(shape_removed)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST_EQ(applied, n); + BOOST_TEST(spec != 0); + } +}; + +void test_remove_comparison_count() +{ + static const int mixed[] = {1, 2, 3, 2, 4, 2, 5, 6, 2, 7}; + static const int edges[] = {2, 1, 3, 4, 5, 2}; + static const int none[] = {1, 3, 5, 7, 9, 11}; + static const int all_rm[] = {2, 2, 2, 2, 2, 2}; + static const int* const sets[] = {mixed, edges, none, all_rm}; + static const std::size_t set_len[] = {10u, 6u, 6u, 6u}; + static const std::size_t sizes[] = {0u, 1u, 2u, 3u, 5u, 6u, 10u}; + + for(std::size_t s = 0; s != sizeof(sets)/sizeof(sets[0]); ++s) { + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) { + const std::size_t n = sizes[i]; + if(n > set_len[s]) + continue; + test_detail::for_each_shape_all + (sets[s], n, shape_filler, remove_comparison_check()); + } + } +} + int main() { test_remove_segmented(); @@ -407,5 +453,6 @@ int main() test_remove_movable_seg(); test_remove_movable_seg2(); test_remove_shape_matrix(); + test_remove_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_replace_if_test.cpp b/experimental/segmented_replace_if_test.cpp index 98f21cc..d1b6b7a 100644 --- a/experimental/segmented_replace_if_test.cpp +++ b/experimental/segmented_replace_if_test.cpp @@ -210,6 +210,49 @@ void test_replace_if_single_segment_sentinel() BOOST_TEST_EQ(*it, expected[i]); } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.replace] mandates "Exactly last - first applications of the +// corresponding predicate", so a segment walked twice or an element skipped is +// caught here even where the rewritten values still come out right. +////////////////////////////////////////////////////////////////////////////// + +struct replace_if_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_replace_if(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, is_negative()), 0); + + BOOST_TEST_EQ(calls.n, n); + BOOST_TEST(spec != 0); + } +}; + +void test_replace_if_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + for(int i = 0; i != 16; ++i) + vals[i] = (i % 2) ? (i + 1) : -(i + 1); + test_detail::for_each_shape_all(vals, n, -6, replace_if_count_check()); + + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + test_detail::for_each_shape_all(vals, n, -6, replace_if_count_check()); + + for(int i = 0; i != 16; ++i) + vals[i] = -(i + 1); + test_detail::for_each_shape_all(vals, n, -6, replace_if_count_check()); + } +} + int main() { test_replace_if_shape_matrix(); @@ -220,5 +263,6 @@ int main() test_replace_if_sentinel_non_segmented(); test_replace_if_seg2(); test_replace_if_single_segment_sentinel(); + test_replace_if_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_replace_test.cpp b/experimental/segmented_replace_test.cpp index 9a0a82b..db5e1d1 100644 --- a/experimental/segmented_replace_test.cpp +++ b/experimental/segmented_replace_test.cpp @@ -177,6 +177,51 @@ void test_replace_single_segment_sentinel() BOOST_TEST_EQ(*it, expected[i]); } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.replace] mandates "Exactly last - first applications of the +// corresponding predicate", so an element compared twice at a segment +// boundary is a conformance failure. There is no predicate overload, so the +// count comes from the value type. +////////////////////////////////////////////////////////////////////////////// + +struct replace_comparison_check +{ + int oldv; + + explicit replace_comparison_check(int o) : oldv(o) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::counted_int_ops().reset(); + segmented_replace(c.begin(), test_detail::iter_at(c, n), + test_detail::counted_int(oldv), test_detail::counted_int(999)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST_EQ(applied, n); + BOOST_TEST(spec != 0); + } +}; + +void test_replace_comparison_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i/2 + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + for(std::size_t v = 0; v <= n/2u + 1u; ++v) { + const int oldv = int(v); + test_detail::for_each_shape_all + (vals, n, oldv, replace_comparison_check(oldv)); + } + } +} + int main() { test_replace_shape_matrix(); @@ -187,5 +232,6 @@ int main() test_replace_sentinel_non_segmented(); test_replace_seg2(); test_replace_single_segment_sentinel(); + test_replace_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_reverse_test.cpp b/experimental/segmented_reverse_test.cpp index e1a5d97..8c4a238 100644 --- a/experimental/segmented_reverse_test.cpp +++ b/experimental/segmented_reverse_test.cpp @@ -229,6 +229,40 @@ void test_reverse_shape_matrix() test_detail::for_each_shape_all(vals, sizes[s], -999, reverse_shape_check()); } +////////////////////////////////////////////////////////////////////////////// +// Swap count. +// +// [alg.reverse] mandates "Exactly (last - first)/2 swaps", so a pair swapped +// twice where the two walking ends cross a segment boundary is a conformance +// failure -- and would also put the pair back where it started. +////////////////////////////////////////////////////////////////////////////// + +struct reverse_swap_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::counted_int_ops().reset(); + segmented_reverse(c.begin(), test_detail::iter_at(c, n)); + const std::size_t applied = test_detail::counted_int_ops().swp; + + BOOST_TEST_EQ(applied, n/2u); + BOOST_TEST(spec != 0); + } +}; + +void test_reverse_swap_count() +{ + int vals[16]; + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + + const std::size_t sizes[] = { 0u, 1u, 2u, 3u, 4u, 5u, 8u, 11u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) + test_detail::for_each_shape_all + (vals, sizes[s], -999, reverse_swap_count_check()); +} + int main() { test_reverse_shape_matrix(); @@ -240,5 +274,6 @@ int main() test_special_segment_conditions(); test_reverse_movable_seg(); test_reverse_movable_seg2(); + test_reverse_swap_count(); return boost::report_errors(); } diff --git a/experimental/segmented_search_n_test.cpp b/experimental/segmented_search_n_test.cpp index de2781f..6cb794f 100644 --- a/experimental/segmented_search_n_test.cpp +++ b/experimental/segmented_search_n_test.cpp @@ -399,6 +399,80 @@ void test_search_n_forward_run_before_last_segment() BOOST_TEST(segmented_search_n(sv.begin(), last, 4, 2) == last); } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.search] mandates "At most last - first comparisons" for search_n -- +// LWG 714 lowered the original N * count bound, so the run scanner is not +// allowed to re-compare the elements of an abandoned partial run. There is +// no predicate overload, so the count comes from the value type. +////////////////////////////////////////////////////////////////////////////// + +struct search_n_comparison_check +{ + int count; + int value; + + search_n_comparison_check(int cnt, int v) : count(cnt), value(v) {} + + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::counted_int_ops().reset(); + segmented_search_n(c.begin(), test_detail::iter_at(c, n), count, + test_detail::counted_int(value)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST(applied <= n); + BOOST_TEST(spec != 0); + } +}; + +void run_search_n_count_shapes(const int* vals, std::size_t n, int count, int value) +{ + test_detail::for_each_shape_all + (vals, n, -999, search_n_comparison_check(count, value)); + test_detail::for_each_shape_all_fwd + (vals, n, -999, search_n_comparison_check(count, value)); +} + +void test_search_n_comparison_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 3u, 5u, 8u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + std::size_t i = 0; + int vals[16] = { 0 }; + + for(int count = 0; count <= int(n) + 1; ++count) { + // A single run of every length at every start position. + for(std::size_t p = 0; p != n; ++p) { + for(std::size_t len = 1u; p + len <= n; ++len) { + for(i = 0; i != n; ++i) + vals[i] = (i >= p && i < p + len) ? 7 : 100 + int(i); + run_search_n_count_shapes(vals, n, count, 7); + } + } + + // Two runs separated by a single hole, so a partial run has to be + // abandoned and restarted without looking at anything twice. + for(std::size_t g = 0; g != n; ++g) { + for(i = 0; i != n; ++i) + vals[i] = (i == g) ? 100 : 7; + run_search_n_count_shapes(vals, n, count, 7); + } + + for(i = 0; i != n; ++i) + vals[i] = (i % 2u == 0u) ? 7 : 100; + run_search_n_count_shapes(vals, n, count, 7); + + for(i = 0; i != n; ++i) + vals[i] = 100 + int(i); + run_search_n_count_shapes(vals, n, count, 7); + } + } +} + int main() { test_search_n_shape_matrix(); @@ -417,5 +491,6 @@ int main() test_search_n_single_segment_seg2_single_inner(); test_search_n_single_segment_forward(); test_search_n_forward_run_before_last_segment(); + test_search_n_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_search_test.cpp b/experimental/segmented_search_test.cpp index 421a6bb..847e11b 100644 --- a/experimental/segmented_search_test.cpp +++ b/experimental/segmented_search_test.cpp @@ -557,6 +557,69 @@ void test_search_forward_match_before_last_segment() == test_detail::iter_at(sv, 7)); } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [alg.search] mandates "At most (last1 - first1) * (last2 - first2) +// applications of the corresponding predicate". There is no predicate +// overload, so the count comes from the value type. +////////////////////////////////////////////////////////////////////////////// + +struct search_comparison_check +{ + template + void operator()(C1& hay, std::size_t n1, const char* s1, + C2& ndl, std::size_t n2, const char* s2) const + { + test_detail::counted_int_ops().reset(); + segmented_search(hay.begin(), test_detail::iter_at(hay, n1), + ndl.begin(), test_detail::iter_at(ndl, n2)); + const std::size_t applied = test_detail::counted_int_ops().cmp; + + BOOST_TEST(applied <= n1 * n2); + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void run_search_count_shapes(const int* hay, std::size_t n1, const int* ndl, std::size_t n2) +{ + test_detail::for_each_shape2_all + (hay, n1, ndl, n2, -999, search_comparison_check()); +} + +void test_search_comparison_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 8u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n1 = sizes[s]; + int hay[16] = { 0 }; + int ndl[16] = { 0 }; + std::size_t i = 0, j = 0; + + // Periodic haystack: many candidate starts per needle, so every false + // start has to be rejected and the re-comparisons pile up fastest. + for(i = 0; i != n1; ++i) + hay[i] = int(i % 3u) + 1; + + const std::size_t maxlen = (n1 + 1u < 4u) ? n1 + 1u : 4u; + run_search_count_shapes(hay, n1, ndl, 0u); + + for(std::size_t len = 1u; len <= maxlen; ++len) { + for(std::size_t off = 0; off + len <= n1; ++off) { + for(j = 0; j != len; ++j) + ndl[j] = hay[off + j]; + run_search_count_shapes(hay, n1, ndl, len); + + ndl[len - 1u] = 500; + run_search_count_shapes(hay, n1, ndl, len); + } + for(j = 0; j != len; ++j) + ndl[j] = 500 + int(j); + run_search_count_shapes(hay, n1, ndl, len); + } + } +} + int main() { test_search_shape_matrix(); @@ -580,5 +643,6 @@ int main() test_search_single_segment_haystack_and_needle(); test_search_single_segment_forward(); test_search_forward_match_before_last_segment(); + test_search_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_set_difference_test.cpp b/experimental/segmented_set_difference_test.cpp index c0d08db..3a1c391 100644 --- a/experimental/segmented_set_difference_test.cpp +++ b/experimental/segmented_set_difference_test.cpp @@ -651,6 +651,70 @@ void test_set_difference_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [set.difference] mandates "At most 2 * ((last1 - first1) + (last2 - first2)) +// - 1 comparisons". The destination is laid out in segments of one, two and +// three elements as well as flat, since it is the destination boundaries that +// make the leaf return mid-merge and the walker call it again. +////////////////////////////////////////////////////////////////////////////// + +struct less_int +{ + bool operator()(int a, int b) const { return a < b; } +}; + +inline std::size_t set_difference_comparison_bound(std::size_t n1, std::size_t n2) +{ + const std::size_t total = n1 + n2; + return total ? 2u*total - 1u : 0u; +} + +struct set_difference_count_check +{ + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + boost::container::vector flat(n1 + n2 + 1u, -1); + { + test_detail::op_counter calls; + segmented_set_difference(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + flat.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_difference_comparison_bound(n1, n2)); + } + + for(std::size_t block = 1u; block <= 3u; ++block) { + test_detail::seg_vector out; + for(std::size_t room = 0; room <= n1 + n2; room += block) + out.add_segment(block, -1); + + test_detail::op_counter calls; + segmented_set_difference(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + out.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_difference_comparison_bound(n1, n2)); + } + + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void test_set_difference_comparison_count() +{ + const int v1[] = {1, 2, 2, 3, 5, 8}; + const int v2[] = {2, 3, 3, 4, 8, 9}; + + static const std::size_t pairs[][2] = + { {0u, 0u}, {0u, 3u}, {3u, 0u}, {1u, 1u}, {2u, 4u}, {4u, 2u}, {5u, 6u} }; + + for(std::size_t p = 0; p != sizeof(pairs)/sizeof(pairs[0]); ++p) + test_detail::for_each_shape2_all + (v1, pairs[p][0], v2, pairs[p][1], -999, set_difference_count_check()); +} + int main() { test_set_difference_shape_matrix(); @@ -678,5 +742,6 @@ int main() test_set_difference_single_segment_sentinel(); test_set_difference_single_segment_with_comp(); + test_set_difference_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_set_intersection_test.cpp b/experimental/segmented_set_intersection_test.cpp index 17fd48c..238b73f 100644 --- a/experimental/segmented_set_intersection_test.cpp +++ b/experimental/segmented_set_intersection_test.cpp @@ -644,6 +644,70 @@ void test_set_intersection_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [set.intersection] mandates "At most 2 * ((last1 - first1) + (last2 - +// first2)) - 1 comparisons". The destination is laid out in segments of one, +// two and three elements as well as flat, since it is the destination +// boundaries that make the leaf return mid-merge and the walker call it again. +////////////////////////////////////////////////////////////////////////////// + +struct less_int +{ + bool operator()(int a, int b) const { return a < b; } +}; + +inline std::size_t set_intersection_comparison_bound(std::size_t n1, std::size_t n2) +{ + const std::size_t total = n1 + n2; + return total ? 2u*total - 1u : 0u; +} + +struct set_intersection_count_check +{ + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + boost::container::vector flat(n1 + n2 + 1u, -1); + { + test_detail::op_counter calls; + segmented_set_intersection(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + flat.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_intersection_comparison_bound(n1, n2)); + } + + for(std::size_t block = 1u; block <= 3u; ++block) { + test_detail::seg_vector out; + for(std::size_t room = 0; room <= n1 + n2; room += block) + out.add_segment(block, -1); + + test_detail::op_counter calls; + segmented_set_intersection(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + out.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_intersection_comparison_bound(n1, n2)); + } + + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void test_set_intersection_comparison_count() +{ + const int v1[] = {1, 2, 2, 3, 5, 8}; + const int v2[] = {2, 3, 3, 4, 8, 9}; + + static const std::size_t pairs[][2] = + { {0u, 0u}, {0u, 3u}, {3u, 0u}, {1u, 1u}, {2u, 4u}, {4u, 2u}, {5u, 6u} }; + + for(std::size_t p = 0; p != sizeof(pairs)/sizeof(pairs[0]); ++p) + test_detail::for_each_shape2_all + (v1, pairs[p][0], v2, pairs[p][1], -999, set_intersection_count_check()); +} + int main() { test_set_intersection_shape_matrix(); @@ -671,5 +735,6 @@ int main() test_set_intersection_single_segment_sentinel(); test_set_intersection_single_segment_with_comp(); + test_set_intersection_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_set_symmetric_difference_test.cpp b/experimental/segmented_set_symmetric_difference_test.cpp index 8b5da69..fec9866 100644 --- a/experimental/segmented_set_symmetric_difference_test.cpp +++ b/experimental/segmented_set_symmetric_difference_test.cpp @@ -670,6 +670,72 @@ void test_set_symmetric_difference_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [set.symmetric.difference] mandates "At most 2 * ((last1 - first1) + (last2 +// - first2)) - 1 comparisons". The destination is laid out in segments of +// one, two and three elements as well as flat, since it is the destination +// boundaries that make the leaf return mid-merge and the walker call it again. +////////////////////////////////////////////////////////////////////////////// + +struct less_int +{ + bool operator()(int a, int b) const { return a < b; } +}; + +inline std::size_t set_symmetric_difference_comparison_bound(std::size_t n1, std::size_t n2) +{ + const std::size_t total = n1 + n2; + return total ? 2u*total - 1u : 0u; +} + +struct set_symmetric_difference_count_check +{ + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + boost::container::vector flat(n1 + n2 + 1u, -1); + { + test_detail::op_counter calls; + segmented_set_symmetric_difference + (c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + flat.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_symmetric_difference_comparison_bound(n1, n2)); + } + + for(std::size_t block = 1u; block <= 3u; ++block) { + test_detail::seg_vector out; + for(std::size_t room = 0; room <= n1 + n2; room += block) + out.add_segment(block, -1); + + test_detail::op_counter calls; + segmented_set_symmetric_difference + (c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + out.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_symmetric_difference_comparison_bound(n1, n2)); + } + + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void test_set_symmetric_difference_comparison_count() +{ + const int v1[] = {1, 2, 2, 3, 5, 8}; + const int v2[] = {2, 3, 3, 4, 8, 9}; + + static const std::size_t pairs[][2] = + { {0u, 0u}, {0u, 3u}, {3u, 0u}, {1u, 1u}, {2u, 4u}, {4u, 2u}, {5u, 6u} }; + + for(std::size_t p = 0; p != sizeof(pairs)/sizeof(pairs[0]); ++p) + test_detail::for_each_shape2_all + (v1, pairs[p][0], v2, pairs[p][1], -999, set_symmetric_difference_count_check()); +} + int main() { test_set_symmetric_difference_shape_matrix(); @@ -697,5 +763,6 @@ int main() test_set_symmetric_difference_single_segment_sentinel(); test_set_symmetric_difference_single_segment_with_comp(); + test_set_symmetric_difference_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_set_union_test.cpp b/experimental/segmented_set_union_test.cpp index 8931283..85f2da9 100644 --- a/experimental/segmented_set_union_test.cpp +++ b/experimental/segmented_set_union_test.cpp @@ -679,6 +679,70 @@ void test_set_union_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Comparison count. +// +// [set.union] mandates "At most 2 * ((last1 - first1) + (last2 - first2)) - 1 +// comparisons". The destination is laid out in segments of one, two and three +// elements as well as flat, since it is the destination boundaries that make +// the leaf return mid-merge and the walker call it again. +////////////////////////////////////////////////////////////////////////////// + +struct less_int +{ + bool operator()(int a, int b) const { return a < b; } +}; + +inline std::size_t set_union_comparison_bound(std::size_t n1, std::size_t n2) +{ + const std::size_t total = n1 + n2; + return total ? 2u*total - 1u : 0u; +} + +struct set_union_count_check +{ + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + boost::container::vector flat(n1 + n2 + 1u, -1); + { + test_detail::op_counter calls; + segmented_set_union(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + flat.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_union_comparison_bound(n1, n2)); + } + + for(std::size_t block = 1u; block <= 3u; ++block) { + test_detail::seg_vector out; + for(std::size_t room = 0; room <= n1 + n2; room += block) + out.add_segment(block, -1); + + test_detail::op_counter calls; + segmented_set_union(c1.begin(), test_detail::iter_at(c1, n1), + c2.begin(), test_detail::iter_at(c2, n2), + out.begin(), test_detail::counting_pred(calls, less_int())); + BOOST_TEST(calls.n <= set_union_comparison_bound(n1, n2)); + } + + BOOST_TEST(s1 != 0 && s2 != 0); + } +}; + +void test_set_union_comparison_count() +{ + const int v1[] = {1, 2, 2, 3, 5, 8}; + const int v2[] = {2, 3, 3, 4, 8, 9}; + + static const std::size_t pairs[][2] = + { {0u, 0u}, {0u, 3u}, {3u, 0u}, {1u, 1u}, {2u, 4u}, {4u, 2u}, {5u, 6u} }; + + for(std::size_t p = 0; p != sizeof(pairs)/sizeof(pairs[0]); ++p) + test_detail::for_each_shape2_all + (v1, pairs[p][0], v2, pairs[p][1], -999, set_union_count_check()); +} + int main() { test_set_union_shape_matrix(); @@ -706,5 +770,6 @@ int main() test_set_union_single_segment_sentinel(); test_set_union_single_segment_with_comp(); + test_set_union_comparison_count(); return boost::report_errors(); } diff --git a/experimental/segmented_stable_partition_test.cpp b/experimental/segmented_stable_partition_test.cpp index c780516..88b0779 100644 --- a/experimental/segmented_stable_partition_test.cpp +++ b/experimental/segmented_stable_partition_test.cpp @@ -215,6 +215,53 @@ void test_stable_partition_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Predicate application count. +// +// [alg.partitions] mandates that stable_partition performs "Exactly N +// applications of the predicate", N = last - first, however much extra memory +// it uses and however the range is segmented. +////////////////////////////////////////////////////////////////////////////// + +struct stable_partition_count_check +{ + template + void operator()(Cont& c, std::size_t n, const char* spec) const + { + test_detail::op_counter calls; + segmented_stable_partition(c.begin(), test_detail::iter_at(c, n), + test_detail::counting_pred(calls, is_even())); + + BOOST_TEST_EQ(calls.n, n); + BOOST_TEST(test_detail::filler_intact(c, n, -999)); + BOOST_TEST(spec != 0); + } +}; + +void test_stable_partition_predicate_count() +{ + const std::size_t sizes[] = { 0u, 1u, 2u, 5u, 12u }; + for(std::size_t s = 0; s != sizeof(sizes)/sizeof(sizes[0]); ++s) { + const std::size_t n = sizes[s]; + int vals[16]; + + for(int i = 0; i != 16; ++i) + vals[i] = i + 1; + test_detail::for_each_shape_all(vals, n, -999, stable_partition_count_check()); + + for(int i = 0; i != 16; ++i) + vals[i] = 2*i + 2; + test_detail::for_each_shape_all(vals, n, -999, stable_partition_count_check()); + for(int i = 0; i != 16; ++i) + vals[i] = 2*i + 1; + test_detail::for_each_shape_all(vals, n, -999, stable_partition_count_check()); + + for(int i = 0; i != 16; ++i) + vals[i] = (i < 8) ? 2*i + 1 : 2*i + 2; + test_detail::for_each_shape_all(vals, n, -999, stable_partition_count_check()); + } +} + int main() { test_stable_partition_shape_matrix(); @@ -225,5 +272,6 @@ int main() test_stable_partition_non_segmented(); test_stable_partition_movable_seg(); test_stable_partition_movable_seg2(); + test_stable_partition_predicate_count(); return boost::report_errors(); } diff --git a/experimental/segmented_swap_ranges_test.cpp b/experimental/segmented_swap_ranges_test.cpp index 26bb5d3..d97fd35 100644 --- a/experimental/segmented_swap_ranges_test.cpp +++ b/experimental/segmented_swap_ranges_test.cpp @@ -552,6 +552,40 @@ void test_swap_ranges_shape_matrix() shape_filler, swap_ranges_shape_check()); } +////////////////////////////////////////////////////////////////////////////// +// Swap count. +// +// [alg.swap] mandates "Exactly last1 - first1 swaps", so a pair swapped twice +// at a segment boundary is a conformance failure -- and, unlike a re-read, it +// would also put the values back where they started. +////////////////////////////////////////////////////////////////////////////// + +struct swap_ranges_count_check +{ + template + void operator()(C1& c1, std::size_t n1, const char* s1, + C2& c2, std::size_t n2, const char* s2) const + { + test_detail::counted_int_ops().reset(); + segmented_swap_ranges(c1.begin(), test_detail::iter_at(c1, n1), c2.begin()); + const std::size_t applied = test_detail::counted_int_ops().swp; + + BOOST_TEST_EQ(applied, n1); + BOOST_TEST(s1 != 0 && s2 != 0 && n2 >= n1); + } +}; + +void test_swap_ranges_swap_count() +{ + static const std::size_t sizes[][2] = + { {0, 0}, {1, 1}, {1, 3}, {2, 2}, {3, 3}, {3, 6}, {5, 5}, {6, 10} }; + + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) + test_detail::for_each_shape2_all + (shape_first_vals, sizes[i][0], shape_second_vals, sizes[i][1], + shape_filler, swap_ranges_count_check()); +} + int main() { test_swap_ranges_full(); @@ -575,5 +609,6 @@ int main() test_swap_ranges_single_segment_second_from_flat(); test_swap_ranges_shape_matrix(); + test_swap_ranges_swap_count(); return boost::report_errors(); } diff --git a/experimental/segmented_test_helper.hpp b/experimental/segmented_test_helper.hpp index f937a7c..23f8fa3 100644 --- a/experimental/segmented_test_helper.hpp +++ b/experimental/segmented_test_helper.hpp @@ -44,6 +44,98 @@ public: friend std::ostream& operator<<(std::ostream& os, const movable_int& m) { return os << m.val_; } }; +//! Applications of a function object, counted outside it. The algorithms take +//! their function objects by value and copy them through several dispatch +//! layers, so a counter kept as a member of the functor would be incremented in +//! a copy the test never sees again. +struct op_counter +{ + std::size_t n; + + op_counter() : n(0) {} + void reset() { n = 0; } +}; + +//! Wraps a callable so that every application bumps an op_counter owned by the +//! caller. Result is the wrapped callable's return type, which C++03 cannot +//! deduce; counting_pred() supplies it for the bool-returning common case. +template +class counting_fun +{ + op_counter* c_; + F f_; + +public: + counting_fun(op_counter& c, F f) : c_(&c), f_(f) {} + + Result operator()() const + { ++c_->n; return f_(); } + + template + Result operator()(T& x) const + { ++c_->n; return f_(x); } + + template + Result operator()(T& x, U& y) const + { ++c_->n; return f_(x, y); } +}; + +//! Predicates and comparisons all return bool, so their result type does not +//! have to be spelled out at every call site. +template +counting_fun counting_pred(op_counter& c, F f) +{ return counting_fun(c, f); } + +//! Operations counted_int performs. The algorithms specified in terms of +//! comparisons, assignments or swaps of the element type take a value rather +//! than a function object, so there is nothing for the test to wrap and the +//! counters have to be reachable from the type's own operators. +struct value_op_counter +{ + std::size_t cmp; + std::size_t assign; + std::size_t swp; + + value_op_counter() : cmp(0), assign(0), swp(0) {} + void reset() { cmp = 0; assign = 0; swp = 0; } +}; + +inline value_op_counter& counted_int_ops() +{ + static value_op_counter c; + return c; +} + +//! int whose comparisons, assignments and swaps bump counted_int_ops(). +//! Construction deliberately does not, so that building a range leaves the +//! counters alone and only the algorithm under test has to be bracketed by a +//! reset. +class counted_int +{ + int v_; + +public: + counted_int(int v = 0) : v_(v) {} + counted_int(const counted_int& o) : v_(o.v_) {} + + int value() const { return v_; } + + counted_int& operator=(const counted_int& o) + { ++counted_int_ops().assign; v_ = o.v_; return *this; } + + friend bool operator==(const counted_int& a, const counted_int& b) + { ++counted_int_ops().cmp; return a.v_ == b.v_; } + + friend bool operator!=(const counted_int& a, const counted_int& b) + { ++counted_int_ops().cmp; return a.v_ != b.v_; } + + friend void swap(counted_int& a, counted_int& b) + { ++counted_int_ops().swp; const int t = a.v_; a.v_ = b.v_; b.v_ = t; } + + friend std::ostream& operator<<(std::ostream& os, const counted_int& c) + { return os << c.v_; } +}; + // Cat selects the advertised iterator category. Algorithms such as // segmented_find_last have distinct segmented implementations for forward and // for bidirectional iterators, so both have to be instantiable from a test. @@ -436,6 +528,20 @@ public: // true while the guard still holds filler, i.e. the // algorithm has not written past the end. // +// Operation counting, for the complexity requirements stated as a number of +// applications of a predicate, comparison or operation. +// op_counter a counter living outside the function object. +// counting_fun(c, f) +// f, with every application counted in c. R is f's +// return type. +// counting_pred(c, f) the same for a bool-returning f, whose result type +// therefore needs no spelling out. +// counted_int element type whose comparisons, assignments and +// swaps are counted, for the algorithms that take a +// value instead of a function object. +// counted_int_ops() the cmp/assign/swp counters counted_int bumps; +// reset() them immediately before the call. +// // Combinators. Each one walks the core m/s specs first, in their original // order, and then the empty-segment ones. Each builds every range afresh for // every combination, so a callable is free to mutate the ranges it is handed. @@ -587,6 +693,7 @@ typename Cont::iterator iter_at(Cont& c, std::size_t n) inline int seg_value_of(int v) { return v; } inline int seg_value_of(const movable_int& m) { return m.value(); } +inline int seg_value_of(const counted_int& c) { return c.value(); } template boost::container::vector flatten_ints(It first, It last) diff --git a/experimental/segmented_transform_test.cpp b/experimental/segmented_transform_test.cpp index 87795dc..70a2023 100644 --- a/experimental/segmented_transform_test.cpp +++ b/experimental/segmented_transform_test.cpp @@ -446,6 +446,61 @@ void test_transform_shape_matrix() } } +////////////////////////////////////////////////////////////////////////////// +// Operation application count. +// +// [alg.transform] mandates "Exactly last1 - first1 applications of op", +// whatever the segmentation of either range, so an element handed to op again +// after the destination walker steps to the next segment is caught here. +////////////////////////////////////////////////////////////////////////////// + +struct transform_count_check +{ + template + void operator()(CSrc& src, std::size_t n_src, const char* src_spec, + CDst& dst, std::size_t n_dst, const char* dst_spec) const + { + test_detail::op_counter calls; + segmented_transform(src.begin(), test_detail::iter_at(src, n_src), dst.begin(), + test_detail::counting_fun(calls, times_two())); + + BOOST_TEST_EQ(calls.n, n_src); + BOOST_TEST(test_detail::filler_intact(dst, n_dst, shape_filler)); + BOOST_TEST(src_spec != 0 && dst_spec != 0); + } +}; + +void test_transform_operation_count() +{ + static const std::size_t sizes[][2] = + { {0, 0}, {1, 1}, {1, 3}, {2, 2}, {3, 3}, {3, 6}, {5, 5}, {6, 10} }; + + for(std::size_t i = 0; i != sizeof(sizes)/sizeof(sizes[0]); ++i) + test_detail::for_each_shape2_all + (shape_src_vals, sizes[i][0], shape_dst_vals, sizes[i][1], + shape_filler, transform_count_check()); + + // Destination segments of a handful of elements each, so that boundary + // crossings dominate rather than being a single mid-range event. + static const std::size_t blocks[] = {1u, 2u, 3u, 8u, 16u}; + const std::size_t n = 64u; + boost::container::vector in; + in.reserve(n); + for(std::size_t i = 0; i != n; ++i) + in.push_back(static_cast(i) + 1); + + for(std::size_t b = 0; b != sizeof(blocks)/sizeof(blocks[0]); ++b) { + test_detail::seg_vector out; + for(std::size_t room = 0; room < n; room += blocks[b]) + out.add_segment(blocks[b], 0); + + test_detail::op_counter calls; + segmented_transform(in.begin(), in.end(), out.begin(), + test_detail::counting_fun(calls, times_two())); + BOOST_TEST_EQ(calls.n, n); + } +} + int main() { test_transform_full_range(); @@ -468,5 +523,6 @@ int main() test_transform_single_segment_dst_from_flat(); test_transform_shape_matrix(); + test_transform_operation_count(); return boost::report_errors(); }