| // -*- C++ -*- |
| //===-- utils.h -----------------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| // File contains common utilities that tests rely on |
| |
| // Do not #include <algorithm>, because if we do we will not detect accidental dependencies. |
| #include <atomic> |
| #include <cstdint> |
| #include <cstdlib> |
| #include <cstring> |
| #include <iostream> |
| #include <iterator> |
| #include <memory> |
| #include <sstream> |
| #include <vector> |
| |
| #include "pstl_test_config.h" |
| |
| namespace TestUtils |
| { |
| |
| typedef double float64_t; |
| typedef float float32_t; |
| |
| template <class T, std::size_t N> |
| constexpr size_t |
| const_size(const T (&array)[N]) noexcept |
| { |
| return N; |
| } |
| |
| template <typename T> |
| class Sequence; |
| |
| // Handy macros for error reporting |
| #define EXPECT_TRUE(condition, message) ::TestUtils::expect(true, condition, __FILE__, __LINE__, message) |
| #define EXPECT_FALSE(condition, message) ::TestUtils::expect(false, condition, __FILE__, __LINE__, message) |
| |
| // Check that expected and actual are equal and have the same type. |
| #define EXPECT_EQ(expected, actual, message) ::TestUtils::expect_equal(expected, actual, __FILE__, __LINE__, message) |
| |
| // Check that sequences started with expected and actual and have had size n are equal and have the same type. |
| #define EXPECT_EQ_N(expected, actual, n, message) \ |
| ::TestUtils::expect_equal(expected, actual, n, __FILE__, __LINE__, message) |
| |
| // Issue error message from outstr, adding a newline. |
| // Real purpose of this routine is to have a place to hang a breakpoint. |
| inline void |
| issue_error_message(std::stringstream& outstr) |
| { |
| outstr << std::endl; |
| std::cerr << outstr.str(); |
| std::exit(EXIT_FAILURE); |
| } |
| |
| inline void |
| expect(bool expected, bool condition, const char* file, int32_t line, const char* message) |
| { |
| if (condition != expected) |
| { |
| std::stringstream outstr; |
| outstr << "error at " << file << ":" << line << " - " << message; |
| issue_error_message(outstr); |
| } |
| } |
| |
| // Do not change signature to const T&. |
| // Function must be able to detect const differences between expected and actual. |
| template <typename T> |
| void |
| expect_equal(T& expected, T& actual, const char* file, int32_t line, const char* message) |
| { |
| if (!(expected == actual)) |
| { |
| std::stringstream outstr; |
| outstr << "error at " << file << ":" << line << " - " << message << ", expected " << expected << " got " |
| << actual; |
| issue_error_message(outstr); |
| } |
| } |
| |
| template <typename T> |
| void |
| expect_equal(Sequence<T>& expected, Sequence<T>& actual, const char* file, int32_t line, const char* message) |
| { |
| size_t n = expected.size(); |
| size_t m = actual.size(); |
| if (n != m) |
| { |
| std::stringstream outstr; |
| outstr << "error at " << file << ":" << line << " - " << message << ", expected sequence of size " << n |
| << " got sequence of size " << m; |
| issue_error_message(outstr); |
| return; |
| } |
| size_t error_count = 0; |
| for (size_t k = 0; k < n && error_count < 10; ++k) |
| { |
| if (!(expected[k] == actual[k])) |
| { |
| std::stringstream outstr; |
| outstr << "error at " << file << ":" << line << " - " << message << ", at index " << k << " expected " |
| << expected[k] << " got " << actual[k]; |
| issue_error_message(outstr); |
| ++error_count; |
| } |
| } |
| } |
| |
| template <typename Iterator1, typename Iterator2, typename Size> |
| void |
| expect_equal(Iterator1 expected_first, Iterator2 actual_first, Size n, const char* file, int32_t line, |
| const char* message) |
| { |
| size_t error_count = 0; |
| for (size_t k = 0; k < n && error_count < 10; ++k, ++expected_first, ++actual_first) |
| { |
| if (!(*expected_first == *actual_first)) |
| { |
| std::stringstream outstr; |
| outstr << "error at " << file << ":" << line << " - " << message << ", at index " << k; |
| issue_error_message(outstr); |
| ++error_count; |
| } |
| } |
| } |
| |
| // ForwardIterator is like type Iterator, but restricted to be a forward iterator. |
| // Only the forward iterator signatures that are necessary for tests are present. |
| // Post-increment in particular is deliberatly omitted since our templates should avoid using it |
| // because of efficiency considerations. |
| template <typename Iterator, typename IteratorTag> |
| class ForwardIterator |
| { |
| public: |
| typedef IteratorTag iterator_category; |
| typedef typename std::iterator_traits<Iterator>::value_type value_type; |
| typedef typename std::iterator_traits<Iterator>::difference_type difference_type; |
| typedef typename std::iterator_traits<Iterator>::pointer pointer; |
| typedef typename std::iterator_traits<Iterator>::reference reference; |
| |
| protected: |
| Iterator my_iterator; |
| typedef value_type element_type; |
| |
| public: |
| ForwardIterator() = default; |
| explicit ForwardIterator(Iterator i) : my_iterator(i) {} |
| reference operator*() const { return *my_iterator; } |
| Iterator operator->() const { return my_iterator; } |
| ForwardIterator |
| operator++() |
| { |
| ++my_iterator; |
| return *this; |
| } |
| ForwardIterator operator++(int32_t) |
| { |
| auto retval = *this; |
| my_iterator++; |
| return retval; |
| } |
| friend bool |
| operator==(const ForwardIterator& i, const ForwardIterator& j) |
| { |
| return i.my_iterator == j.my_iterator; |
| } |
| friend bool |
| operator!=(const ForwardIterator& i, const ForwardIterator& j) |
| { |
| return i.my_iterator != j.my_iterator; |
| } |
| |
| Iterator |
| iterator() const |
| { |
| return my_iterator; |
| } |
| }; |
| |
| template <typename Iterator, typename IteratorTag> |
| class BidirectionalIterator : public ForwardIterator<Iterator, IteratorTag> |
| { |
| typedef ForwardIterator<Iterator, IteratorTag> base_type; |
| |
| public: |
| BidirectionalIterator() = default; |
| explicit BidirectionalIterator(Iterator i) : base_type(i) {} |
| BidirectionalIterator(const base_type& i) : base_type(i.iterator()) {} |
| |
| BidirectionalIterator |
| operator++() |
| { |
| ++base_type::my_iterator; |
| return *this; |
| } |
| BidirectionalIterator |
| operator--() |
| { |
| --base_type::my_iterator; |
| return *this; |
| } |
| BidirectionalIterator operator++(int32_t) |
| { |
| auto retval = *this; |
| base_type::my_iterator++; |
| return retval; |
| } |
| BidirectionalIterator operator--(int32_t) |
| { |
| auto retval = *this; |
| base_type::my_iterator--; |
| return retval; |
| } |
| }; |
| |
| template <typename Iterator, typename F> |
| void |
| fill_data(Iterator first, Iterator last, F f) |
| { |
| typedef typename std::iterator_traits<Iterator>::value_type T; |
| for (std::size_t i = 0; first != last; ++first, ++i) |
| { |
| *first = T(f(i)); |
| } |
| } |
| |
| // Sequence<T> is a container of a sequence of T with lots of kinds of iterators. |
| // Prefixes on begin/end mean: |
| // c = "const" |
| // f = "forward" |
| // No prefix indicates non-const random-access iterator. |
| template <typename T> |
| class Sequence |
| { |
| std::vector<T> m_storage; |
| |
| public: |
| typedef typename std::vector<T>::iterator iterator; |
| typedef typename std::vector<T>::const_iterator const_iterator; |
| typedef ForwardIterator<iterator, std::forward_iterator_tag> forward_iterator; |
| typedef ForwardIterator<const_iterator, std::forward_iterator_tag> const_forward_iterator; |
| |
| typedef BidirectionalIterator<iterator, std::bidirectional_iterator_tag> bidirectional_iterator; |
| typedef BidirectionalIterator<const_iterator, std::bidirectional_iterator_tag> const_bidirectional_iterator; |
| |
| typedef T value_type; |
| explicit Sequence(size_t size) : m_storage(size) {} |
| |
| // Construct sequence [f(0), f(1), ... f(size-1)] |
| // f can rely on its invocations being sequential from 0 to size-1. |
| template <typename Func> |
| Sequence(size_t size, Func f) |
| { |
| m_storage.reserve(size); |
| // Use push_back because T might not have a default constructor |
| for (size_t k = 0; k < size; ++k) |
| m_storage.push_back(T(f(k))); |
| } |
| Sequence(const std::initializer_list<T>& data) : m_storage(data) {} |
| |
| const_iterator |
| begin() const |
| { |
| return m_storage.begin(); |
| } |
| const_iterator |
| end() const |
| { |
| return m_storage.end(); |
| } |
| iterator |
| begin() |
| { |
| return m_storage.begin(); |
| } |
| iterator |
| end() |
| { |
| return m_storage.end(); |
| } |
| const_iterator |
| cbegin() const |
| { |
| return m_storage.cbegin(); |
| } |
| const_iterator |
| cend() const |
| { |
| return m_storage.cend(); |
| } |
| forward_iterator |
| fbegin() |
| { |
| return forward_iterator(m_storage.begin()); |
| } |
| forward_iterator |
| fend() |
| { |
| return forward_iterator(m_storage.end()); |
| } |
| const_forward_iterator |
| cfbegin() const |
| { |
| return const_forward_iterator(m_storage.cbegin()); |
| } |
| const_forward_iterator |
| cfend() const |
| { |
| return const_forward_iterator(m_storage.cend()); |
| } |
| const_forward_iterator |
| fbegin() const |
| { |
| return const_forward_iterator(m_storage.cbegin()); |
| } |
| const_forward_iterator |
| fend() const |
| { |
| return const_forward_iterator(m_storage.cend()); |
| } |
| |
| const_bidirectional_iterator |
| cbibegin() const |
| { |
| return const_bidirectional_iterator(m_storage.cbegin()); |
| } |
| const_bidirectional_iterator |
| cbiend() const |
| { |
| return const_bidirectional_iterator(m_storage.cend()); |
| } |
| |
| bidirectional_iterator |
| bibegin() |
| { |
| return bidirectional_iterator(m_storage.begin()); |
| } |
| bidirectional_iterator |
| biend() |
| { |
| return bidirectional_iterator(m_storage.end()); |
| } |
| |
| std::size_t |
| size() const |
| { |
| return m_storage.size(); |
| } |
| const T* |
| data() const |
| { |
| return m_storage.data(); |
| } |
| typename std::vector<T>::reference operator[](size_t j) { return m_storage[j]; } |
| const T& operator[](size_t j) const { return m_storage[j]; } |
| |
| // Fill with given value |
| void |
| fill(const T& value) |
| { |
| for (size_t i = 0; i < m_storage.size(); i++) |
| m_storage[i] = value; |
| } |
| |
| void |
| print() const; |
| |
| template <typename Func> |
| void |
| fill(Func f) |
| { |
| fill_data(m_storage.begin(), m_storage.end(), f); |
| } |
| }; |
| |
| template <typename T> |
| void |
| Sequence<T>::print() const |
| { |
| std::cout << "size = " << size() << ": { "; |
| std::copy(begin(), end(), std::ostream_iterator<T>(std::cout, " ")); |
| std::cout << " } " << std::endl; |
| } |
| |
| // Predicates for algorithms |
| template <typename DataType> |
| struct is_equal_to |
| { |
| is_equal_to(const DataType& expected) : m_expected(expected) {} |
| bool |
| operator()(const DataType& actual) const |
| { |
| return actual == m_expected; |
| } |
| |
| private: |
| DataType m_expected; |
| }; |
| |
| // Low-quality hash function, returns value between 0 and (1<<bits)-1 |
| // Warning: low-order bits are quite predictable. |
| inline size_t |
| HashBits(size_t i, size_t bits) |
| { |
| size_t mask = bits >= 8 * sizeof(size_t) ? ~size_t(0) : (size_t(1) << bits) - 1; |
| return (424157 * i ^ 0x24aFa) & mask; |
| } |
| |
| // Stateful unary op |
| template <typename T, typename U> |
| class Complement |
| { |
| int32_t val; |
| |
| public: |
| Complement(T v) : val(v) {} |
| U |
| operator()(const T& x) const |
| { |
| return U(val - x); |
| } |
| }; |
| |
| // Tag used to prevent accidental use of converting constructor, even if use is explicit. |
| struct OddTag |
| { |
| }; |
| |
| class Sum; |
| |
| // Type with limited set of operations. Not default-constructible. |
| // Only available operator is "==". |
| // Typically used as value type in tests. |
| class Number |
| { |
| int32_t value; |
| friend class Add; |
| friend class Sum; |
| friend class IsMultiple; |
| friend class Congruent; |
| friend Sum |
| operator+(const Sum& x, const Sum& y); |
| |
| public: |
| Number(int32_t val, OddTag) : value(val) {} |
| friend bool |
| operator==(const Number& x, const Number& y) |
| { |
| return x.value == y.value; |
| } |
| friend std::ostream& |
| operator<<(std::ostream& o, const Number& d) |
| { |
| return o << d.value; |
| } |
| }; |
| |
| // Stateful predicate for Number. Not default-constructible. |
| class IsMultiple |
| { |
| long modulus; |
| |
| public: |
| // True if x is multiple of modulus |
| bool |
| operator()(Number x) const |
| { |
| return x.value % modulus == 0; |
| } |
| IsMultiple(long modulus_, OddTag) : modulus(modulus_) {} |
| }; |
| |
| // Stateful equivalence-class predicate for Number. Not default-constructible. |
| class Congruent |
| { |
| long modulus; |
| |
| public: |
| // True if x and y have same remainder for the given modulus. |
| // Note: this is not quite the same as "equivalent modulo modulus" when x and y have different |
| // sign, but nonetheless AreCongruent is still an equivalence relationship, which is all |
| // we need for testing. |
| bool |
| operator()(Number x, Number y) const |
| { |
| return x.value % modulus == y.value % modulus; |
| } |
| Congruent(long modulus_, OddTag) : modulus(modulus_) {} |
| }; |
| |
| // Stateful reduction operation for Number |
| class Add |
| { |
| long bias; |
| |
| public: |
| explicit Add(OddTag) : bias(1) {} |
| Number |
| operator()(Number x, const Number& y) |
| { |
| return Number(x.value + y.value + (bias - 1), OddTag()); |
| } |
| }; |
| |
| // Class similar to Number, but has default constructor and +. |
| class Sum : public Number |
| { |
| public: |
| Sum() : Number(0, OddTag()) {} |
| Sum(long x, OddTag) : Number(x, OddTag()) {} |
| friend Sum |
| operator+(const Sum& x, const Sum& y) |
| { |
| return Sum(x.value + y.value, OddTag()); |
| } |
| }; |
| |
| // Type with limited set of operations, which includes an associative but not commutative operation. |
| // Not default-constructible. |
| // Typically used as value type in tests involving "GENERALIZED_NONCOMMUTATIVE_SUM". |
| class MonoidElement |
| { |
| size_t a, b; |
| |
| public: |
| MonoidElement(size_t a_, size_t b_, OddTag) : a(a_), b(b_) {} |
| friend bool |
| operator==(const MonoidElement& x, const MonoidElement& y) |
| { |
| return x.a == y.a && x.b == y.b; |
| } |
| friend std::ostream& |
| operator<<(std::ostream& o, const MonoidElement& x) |
| { |
| return o << "[" << x.a << ".." << x.b << ")"; |
| } |
| friend class AssocOp; |
| }; |
| |
| // Stateful associative op for MonoidElement |
| // It's not really a monoid since the operation is not allowed for any two elements. |
| // But it's good enough for testing. |
| class AssocOp |
| { |
| unsigned c; |
| |
| public: |
| explicit AssocOp(OddTag) : c(5) {} |
| MonoidElement |
| operator()(const MonoidElement& x, const MonoidElement& y) |
| { |
| unsigned d = 5; |
| EXPECT_EQ(d, c, "state lost"); |
| EXPECT_EQ(x.b, y.a, "commuted?"); |
| |
| return MonoidElement(x.a, y.b, OddTag()); |
| } |
| }; |
| |
| // Multiplication of matrix is an associative but not commutative operation |
| // Typically used as value type in tests involving "GENERALIZED_NONCOMMUTATIVE_SUM". |
| template <typename T> |
| struct Matrix2x2 |
| { |
| T a[2][2]; |
| Matrix2x2() : a{{1, 0}, {0, 1}} {} |
| Matrix2x2(T x, T y) : a{{0, x}, {x, y}} {} |
| #if !_PSTL_ICL_19_VC14_VC141_TEST_SCAN_RELEASE_BROKEN |
| Matrix2x2(const Matrix2x2& m) : a{{m.a[0][0], m.a[0][1]}, {m.a[1][0], m.a[1][1]}} {} |
| Matrix2x2& |
| operator=(const Matrix2x2& m) |
| { |
| a[0][0] = m.a[0][0], a[0][1] = m.a[0][1], a[1][0] = m.a[1][0], a[1][1] = m.a[1][1]; |
| return *this; |
| } |
| #endif |
| }; |
| |
| template <typename T> |
| bool |
| operator==(const Matrix2x2<T>& left, const Matrix2x2<T>& right) |
| { |
| return left.a[0][0] == right.a[0][0] && left.a[0][1] == right.a[0][1] && left.a[1][0] == right.a[1][0] && |
| left.a[1][1] == right.a[1][1]; |
| } |
| |
| template <typename T> |
| Matrix2x2<T> |
| multiply_matrix(const Matrix2x2<T>& left, const Matrix2x2<T>& right) |
| { |
| Matrix2x2<T> result; |
| for (int32_t i = 0; i < 2; ++i) |
| { |
| for (int32_t j = 0; j < 2; ++j) |
| { |
| result.a[i][j] = left.a[i][0] * right.a[0][j] + left.a[i][1] * right.a[1][j]; |
| } |
| } |
| return result; |
| } |
| |
| //============================================================================ |
| // Adapters for creating different types of iterators. |
| // |
| // In this block we implemented some adapters for creating differnet types of iterators. |
| // It's needed for extending the unit testing of Parallel STL algorithms. |
| // We have adapters for iterators with different tags (forward_iterator_tag, bidirectional_iterator_tag), reverse iterators. |
| // The input iterator should be const or non-const, non-reverse random access iterator. |
| // Iterator creates in "MakeIterator": |
| // firstly, iterator is "packed" by "IteratorTypeAdapter" (creating forward or bidirectional iterator) |
| // then iterator is "packed" by "ReverseAdapter" (if it's possible) |
| // So, from input iterator we may create, for example, reverse bidirectional iterator. |
| // "Main" functor for testing iterators is named "invoke_on_all_iterator_types". |
| |
| // Base adapter |
| template <typename Iterator> |
| struct BaseAdapter |
| { |
| typedef Iterator iterator_type; |
| iterator_type |
| operator()(Iterator it) |
| { |
| return it; |
| } |
| }; |
| |
| // Check if the iterator is reverse iterator |
| // Note: it works only for iterators that created by std::reverse_iterator |
| template <typename NotReverseIterator> |
| struct isReverse : std::false_type |
| { |
| }; |
| |
| template <typename Iterator> |
| struct isReverse<std::reverse_iterator<Iterator>> : std::true_type |
| { |
| }; |
| |
| // Reverse adapter |
| template <typename Iterator, typename IsReverse> |
| struct ReverseAdapter |
| { |
| typedef std::reverse_iterator<Iterator> iterator_type; |
| iterator_type |
| operator()(Iterator it) |
| { |
| #if _PSTL_CPP14_MAKE_REVERSE_ITERATOR_PRESENT |
| return std::make_reverse_iterator(it); |
| #else |
| return iterator_type(it); |
| #endif |
| } |
| }; |
| |
| // Non-reverse adapter |
| template <typename Iterator> |
| struct ReverseAdapter<Iterator, std::false_type> : BaseAdapter<Iterator> |
| { |
| }; |
| |
| // Iterator adapter by type (by default std::random_access_iterator_tag) |
| template <typename Iterator, typename IteratorTag> |
| struct IteratorTypeAdapter : BaseAdapter<Iterator> |
| { |
| }; |
| |
| // Iterator adapter for forward iterator |
| template <typename Iterator> |
| struct IteratorTypeAdapter<Iterator, std::forward_iterator_tag> |
| { |
| typedef ForwardIterator<Iterator, std::forward_iterator_tag> iterator_type; |
| iterator_type |
| operator()(Iterator it) |
| { |
| return iterator_type(it); |
| } |
| }; |
| |
| // Iterator adapter for bidirectional iterator |
| template <typename Iterator> |
| struct IteratorTypeAdapter<Iterator, std::bidirectional_iterator_tag> |
| { |
| typedef BidirectionalIterator<Iterator, std::bidirectional_iterator_tag> iterator_type; |
| iterator_type |
| operator()(Iterator it) |
| { |
| return iterator_type(it); |
| } |
| }; |
| |
| //For creating iterator with new type |
| template <typename InputIterator, typename IteratorTag, typename IsReverse> |
| struct MakeIterator |
| { |
| typedef IteratorTypeAdapter<InputIterator, IteratorTag> IterByType; |
| typedef ReverseAdapter<typename IterByType::iterator_type, IsReverse> ReverseIter; |
| |
| typename ReverseIter::iterator_type |
| operator()(InputIterator it) |
| { |
| return ReverseIter()(IterByType()(it)); |
| } |
| }; |
| |
| // Useful constant variables |
| constexpr std::size_t GuardSize = 5; |
| constexpr std::ptrdiff_t sizeLimit = 1000; |
| |
| template <typename Iter, typename Void = void> // local iterator_traits for non-iterators |
| struct iterator_traits_ |
| { |
| }; |
| |
| template <typename Iter> // For iterators |
| struct iterator_traits_<Iter, |
| typename std::enable_if<!std::is_void<typename Iter::iterator_category>::value, void>::type> |
| { |
| typedef typename Iter::iterator_category iterator_category; |
| }; |
| |
| template <typename T> // For pointers |
| struct iterator_traits_<T*> |
| { |
| typedef std::random_access_iterator_tag iterator_category; |
| }; |
| |
| // is iterator Iter has tag Tag |
| template <typename Iter, typename Tag> |
| using is_same_iterator_category = std::is_same<typename iterator_traits_<Iter>::iterator_category, Tag>; |
| |
| // if we run with reverse or const iterators we shouldn't test the large range |
| template <typename IsReverse, typename IsConst> |
| struct invoke_if_ |
| { |
| template <typename Op, typename... Rest> |
| void |
| operator()(bool is_allow, Op op, Rest&&... rest) |
| { |
| if (is_allow) |
| op(std::forward<Rest>(rest)...); |
| } |
| }; |
| template <> |
| struct invoke_if_<std::false_type, std::false_type> |
| { |
| template <typename Op, typename... Rest> |
| void |
| operator()(bool, Op op, Rest&&... rest) |
| { |
| op(std::forward<Rest>(rest)...); |
| } |
| }; |
| |
| // Base non_const_wrapper struct. It is used to distinguish non_const testcases |
| // from a regular one. For non_const testcases only compilation is checked. |
| struct non_const_wrapper |
| { |
| }; |
| |
| // Generic wrapper to specify iterator type to execute callable Op on. |
| // The condition can be either positive(Op is executed only with IteratorTag) |
| // or negative(Op is executed with every type of iterators except IteratorTag) |
| template <typename Op, typename IteratorTag, bool IsPositiveCondition = true> |
| struct non_const_wrapper_tagged : non_const_wrapper |
| { |
| template <typename Policy, typename Iterator> |
| typename std::enable_if<IsPositiveCondition == is_same_iterator_category<Iterator, IteratorTag>::value, void>::type |
| operator()(Policy&& exec, Iterator iter) |
| { |
| Op()(exec, iter); |
| } |
| |
| template <typename Policy, typename InputIterator, typename OutputIterator> |
| typename std::enable_if<IsPositiveCondition == is_same_iterator_category<OutputIterator, IteratorTag>::value, |
| void>::type |
| operator()(Policy&& exec, InputIterator input_iter, OutputIterator out_iter) |
| { |
| Op()(exec, input_iter, out_iter); |
| } |
| |
| template <typename Policy, typename Iterator> |
| typename std::enable_if<IsPositiveCondition != is_same_iterator_category<Iterator, IteratorTag>::value, void>::type |
| operator()(Policy&&, Iterator) |
| { |
| } |
| |
| template <typename Policy, typename InputIterator, typename OutputIterator> |
| typename std::enable_if<IsPositiveCondition != is_same_iterator_category<OutputIterator, IteratorTag>::value, |
| void>::type |
| operator()(Policy&&, InputIterator, OutputIterator) |
| { |
| } |
| }; |
| |
| // These run_for_* structures specify with which types of iterators callable object Op |
| // should be executed. |
| template <typename Op> |
| struct run_for_rnd : non_const_wrapper_tagged<Op, std::random_access_iterator_tag> |
| { |
| }; |
| |
| template <typename Op> |
| struct run_for_rnd_bi : non_const_wrapper_tagged<Op, std::forward_iterator_tag, false> |
| { |
| }; |
| |
| template <typename Op> |
| struct run_for_rnd_fw : non_const_wrapper_tagged<Op, std::bidirectional_iterator_tag, false> |
| { |
| }; |
| |
| // Invoker for different types of iterators. |
| template <typename IteratorTag, typename IsReverse> |
| struct iterator_invoker |
| { |
| template <typename Iterator> |
| using make_iterator = MakeIterator<Iterator, IteratorTag, IsReverse>; |
| template <typename Iterator> |
| using IsConst = typename std::is_const< |
| typename std::remove_pointer<typename std::iterator_traits<Iterator>::pointer>::type>::type; |
| template <typename Iterator> |
| using invoke_if = invoke_if_<IsReverse, IsConst<Iterator>>; |
| |
| // A single iterator version which is used for non_const testcases |
| template <typename Policy, typename Op, typename Iterator> |
| typename std::enable_if<is_same_iterator_category<Iterator, std::random_access_iterator_tag>::value && |
| std::is_base_of<non_const_wrapper, Op>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, Iterator iter) |
| { |
| op(std::forward<Policy>(exec), make_iterator<Iterator>()(iter)); |
| } |
| |
| // A version with 2 iterators which is used for non_const testcases |
| template <typename Policy, typename Op, typename InputIterator, typename OutputIterator> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value && |
| std::is_base_of<non_const_wrapper, Op>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator input_iter, OutputIterator out_iter) |
| { |
| op(std::forward<Policy>(exec), make_iterator<InputIterator>()(input_iter), |
| make_iterator<OutputIterator>()(out_iter)); |
| } |
| |
| template <typename Policy, typename Op, typename Iterator, typename Size, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<Iterator, std::random_access_iterator_tag>::value, void>::type |
| operator()(Policy&& exec, Op op, Iterator begin, Size n, Rest&&... rest) |
| { |
| invoke_if<Iterator>()(n <= sizeLimit, op, exec, make_iterator<Iterator>()(begin), n, |
| std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename Iterator, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<Iterator, std::random_access_iterator_tag>::value && |
| !std::is_base_of<non_const_wrapper, Op>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, Iterator inputBegin, Iterator inputEnd, Rest&&... rest) |
| { |
| invoke_if<Iterator>()(std::distance(inputBegin, inputEnd) <= sizeLimit, op, exec, |
| make_iterator<Iterator>()(inputBegin), make_iterator<Iterator>()(inputEnd), |
| std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin, |
| Rest&&... rest) |
| { |
| invoke_if<InputIterator>()(std::distance(inputBegin, inputEnd) <= sizeLimit, op, exec, |
| make_iterator<InputIterator>()(inputBegin), make_iterator<InputIterator>()(inputEnd), |
| make_iterator<OutputIterator>()(outputBegin), std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin, |
| OutputIterator outputEnd, Rest&&... rest) |
| { |
| invoke_if<InputIterator>()(std::distance(inputBegin, inputEnd) <= sizeLimit, op, exec, |
| make_iterator<InputIterator>()(inputBegin), make_iterator<InputIterator>()(inputEnd), |
| make_iterator<OutputIterator>()(outputBegin), |
| make_iterator<OutputIterator>()(outputEnd), std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename InputIterator1, typename InputIterator2, typename OutputIterator, |
| typename... Rest> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator1 inputBegin1, InputIterator1 inputEnd1, InputIterator2 inputBegin2, |
| InputIterator2 inputEnd2, OutputIterator outputBegin, OutputIterator outputEnd, Rest&&... rest) |
| { |
| invoke_if<InputIterator1>()( |
| std::distance(inputBegin1, inputEnd1) <= sizeLimit, op, exec, make_iterator<InputIterator1>()(inputBegin1), |
| make_iterator<InputIterator1>()(inputEnd1), make_iterator<InputIterator2>()(inputBegin2), |
| make_iterator<InputIterator2>()(inputEnd2), make_iterator<OutputIterator>()(outputBegin), |
| make_iterator<OutputIterator>()(outputEnd), std::forward<Rest>(rest)...); |
| } |
| }; |
| |
| // Invoker for reverse iterators only |
| // Note: if we run with reverse iterators we shouldn't test the large range |
| template <typename IteratorTag> |
| struct iterator_invoker<IteratorTag, /* IsReverse = */ std::true_type> |
| { |
| |
| template <typename Iterator> |
| using make_iterator = MakeIterator<Iterator, IteratorTag, std::true_type>; |
| |
| // A single iterator version which is used for non_const testcases |
| template <typename Policy, typename Op, typename Iterator> |
| typename std::enable_if<is_same_iterator_category<Iterator, std::random_access_iterator_tag>::value && |
| std::is_base_of<non_const_wrapper, Op>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, Iterator iter) |
| { |
| op(std::forward<Policy>(exec), make_iterator<Iterator>()(iter)); |
| } |
| |
| // A version with 2 iterators which is used for non_const testcases |
| template <typename Policy, typename Op, typename InputIterator, typename OutputIterator> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value && |
| std::is_base_of<non_const_wrapper, Op>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator input_iter, OutputIterator out_iter) |
| { |
| op(std::forward<Policy>(exec), make_iterator<InputIterator>()(input_iter), |
| make_iterator<OutputIterator>()(out_iter)); |
| } |
| |
| template <typename Policy, typename Op, typename Iterator, typename Size, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<Iterator, std::random_access_iterator_tag>::value, void>::type |
| operator()(Policy&& exec, Op op, Iterator begin, Size n, Rest&&... rest) |
| { |
| if (n <= sizeLimit) |
| op(exec, make_iterator<Iterator>()(begin + n), n, std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename Iterator, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<Iterator, std::random_access_iterator_tag>::value && |
| !std::is_base_of<non_const_wrapper, Op>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, Iterator inputBegin, Iterator inputEnd, Rest&&... rest) |
| { |
| if (std::distance(inputBegin, inputEnd) <= sizeLimit) |
| op(exec, make_iterator<Iterator>()(inputEnd), make_iterator<Iterator>()(inputBegin), |
| std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin, |
| Rest&&... rest) |
| { |
| if (std::distance(inputBegin, inputEnd) <= sizeLimit) |
| op(exec, make_iterator<InputIterator>()(inputEnd), make_iterator<InputIterator>()(inputBegin), |
| make_iterator<OutputIterator>()(outputBegin + (inputEnd - inputBegin)), std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename InputIterator, typename OutputIterator, typename... Rest> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator inputBegin, InputIterator inputEnd, OutputIterator outputBegin, |
| OutputIterator outputEnd, Rest&&... rest) |
| { |
| if (std::distance(inputBegin, inputEnd) <= sizeLimit) |
| op(exec, make_iterator<InputIterator>()(inputEnd), make_iterator<InputIterator>()(inputBegin), |
| make_iterator<OutputIterator>()(outputEnd), make_iterator<OutputIterator>()(outputBegin), |
| std::forward<Rest>(rest)...); |
| } |
| |
| template <typename Policy, typename Op, typename InputIterator1, typename InputIterator2, typename OutputIterator, |
| typename... Rest> |
| typename std::enable_if<is_same_iterator_category<OutputIterator, std::random_access_iterator_tag>::value, |
| void>::type |
| operator()(Policy&& exec, Op op, InputIterator1 inputBegin1, InputIterator1 inputEnd1, InputIterator2 inputBegin2, |
| InputIterator2 inputEnd2, OutputIterator outputBegin, OutputIterator outputEnd, Rest&&... rest) |
| { |
| if (std::distance(inputBegin1, inputEnd1) <= sizeLimit) |
| op(exec, make_iterator<InputIterator1>()(inputEnd1), make_iterator<InputIterator1>()(inputBegin1), |
| make_iterator<InputIterator2>()(inputEnd2), make_iterator<InputIterator2>()(inputBegin2), |
| make_iterator<OutputIterator>()(outputEnd), make_iterator<OutputIterator>()(outputBegin), |
| std::forward<Rest>(rest)...); |
| } |
| }; |
| |
| // We can't create reverse iterator from forward iterator |
| template <> |
| struct iterator_invoker<std::forward_iterator_tag, /*isReverse=*/std::true_type> |
| { |
| template <typename... Rest> |
| void |
| operator()(Rest&&...) |
| { |
| } |
| }; |
| |
| template <typename IsReverse> |
| struct reverse_invoker |
| { |
| template <typename... Rest> |
| void |
| operator()(Rest&&... rest) |
| { |
| // Random-access iterator |
| iterator_invoker<std::random_access_iterator_tag, IsReverse>()(std::forward<Rest>(rest)...); |
| |
| // Forward iterator |
| iterator_invoker<std::forward_iterator_tag, IsReverse>()(std::forward<Rest>(rest)...); |
| |
| // Bidirectional iterator |
| iterator_invoker<std::bidirectional_iterator_tag, IsReverse>()(std::forward<Rest>(rest)...); |
| } |
| }; |
| |
| struct invoke_on_all_iterator_types |
| { |
| template <typename... Rest> |
| void |
| operator()(Rest&&... rest) |
| { |
| reverse_invoker</* IsReverse = */ std::false_type>()(std::forward<Rest>(rest)...); |
| reverse_invoker</* IsReverse = */ std::true_type>()(std::forward<Rest>(rest)...); |
| } |
| }; |
| //============================================================================ |
| |
| // Invoke op(policy,rest...) for each possible policy. |
| template <typename Op, typename... T> |
| void |
| invoke_on_all_policies(Op op, T&&... rest) |
| { |
| using namespace __pstl::execution; |
| |
| // Try static execution policies |
| invoke_on_all_iterator_types()(seq, op, std::forward<T>(rest)...); |
| invoke_on_all_iterator_types()(unseq, op, std::forward<T>(rest)...); |
| invoke_on_all_iterator_types()(par, op, std::forward<T>(rest)...); |
| invoke_on_all_iterator_types()(par_unseq, op, std::forward<T>(rest)...); |
| } |
| |
| template <typename F> |
| struct NonConstAdapter |
| { |
| F my_f; |
| NonConstAdapter(const F& f) : my_f(f) {} |
| |
| template <typename... Types> |
| auto |
| operator()(Types&&... args) -> decltype(std::declval<F>(). |
| operator()(std::forward<Types>(args)...)) |
| { |
| return my_f(std::forward<Types>(args)...); |
| } |
| }; |
| |
| template <typename F> |
| NonConstAdapter<F> |
| non_const(const F& f) |
| { |
| return NonConstAdapter<F>(f); |
| } |
| |
| // Wrapper for types. It's need for counting of constructing and destructing objects |
| template <typename T> |
| class Wrapper |
| { |
| public: |
| Wrapper() |
| { |
| my_field = std::shared_ptr<T>(new T()); |
| ++my_count; |
| } |
| Wrapper(const T& input) |
| { |
| my_field = std::shared_ptr<T>(new T(input)); |
| ++my_count; |
| } |
| Wrapper(const Wrapper& input) |
| { |
| my_field = input.my_field; |
| ++my_count; |
| } |
| Wrapper(Wrapper&& input) |
| { |
| my_field = input.my_field; |
| input.my_field = nullptr; |
| ++move_count; |
| } |
| Wrapper& |
| operator=(const Wrapper& input) |
| { |
| my_field = input.my_field; |
| return *this; |
| } |
| Wrapper& |
| operator=(Wrapper&& input) |
| { |
| my_field = input.my_field; |
| input.my_field = nullptr; |
| ++move_count; |
| return *this; |
| } |
| bool |
| operator==(const Wrapper& input) const |
| { |
| return my_field == input.my_field; |
| } |
| bool |
| operator<(const Wrapper& input) const |
| { |
| return *my_field < *input.my_field; |
| } |
| bool |
| operator>(const Wrapper& input) const |
| { |
| return *my_field > *input.my_field; |
| } |
| friend std::ostream& |
| operator<<(std::ostream& stream, const Wrapper& input) |
| { |
| return stream << *(input.my_field); |
| } |
| ~Wrapper() |
| { |
| --my_count; |
| if (move_count > 0) |
| { |
| --move_count; |
| } |
| } |
| T* |
| get_my_field() const |
| { |
| return my_field.get(); |
| }; |
| static size_t |
| Count() |
| { |
| return my_count; |
| } |
| static size_t |
| MoveCount() |
| { |
| return move_count; |
| } |
| static void |
| SetCount(const size_t& n) |
| { |
| my_count = n; |
| } |
| static void |
| SetMoveCount(const size_t& n) |
| { |
| move_count = n; |
| } |
| |
| private: |
| static std::atomic<size_t> my_count; |
| static std::atomic<size_t> move_count; |
| std::shared_ptr<T> my_field; |
| }; |
| |
| template <typename T> |
| std::atomic<size_t> Wrapper<T>::my_count = {0}; |
| |
| template <typename T> |
| std::atomic<size_t> Wrapper<T>::move_count = {0}; |
| |
| template <typename InputIterator, typename T, typename BinaryOperation, typename UnaryOperation> |
| T |
| transform_reduce_serial(InputIterator first, InputIterator last, T init, BinaryOperation binary_op, |
| UnaryOperation unary_op) noexcept |
| { |
| for (; first != last; ++first) |
| { |
| init = binary_op(init, unary_op(*first)); |
| } |
| return init; |
| } |
| |
| static const char* |
| done() |
| { |
| #if _PSTL_TEST_SUCCESSFUL_KEYWORD |
| return "done"; |
| #else |
| return "passed"; |
| #endif |
| } |
| |
| // test_algo_basic_* functions are used to execute |
| // f on a very basic sequence of elements of type T. |
| |
| // Should be used with unary predicate |
| template <typename T, typename F> |
| static void |
| test_algo_basic_single(F&& f) |
| { |
| size_t N = 10; |
| Sequence<T> in(N, [](size_t v) -> T { return T(v); }); |
| |
| invoke_on_all_policies(f, in.begin()); |
| } |
| |
| // Should be used with binary predicate |
| template <typename T, typename F> |
| static void |
| test_algo_basic_double(F&& f) |
| { |
| size_t N = 10; |
| Sequence<T> in(N, [](size_t v) -> T { return T(v); }); |
| Sequence<T> out(N, [](size_t v) -> T { return T(v); }); |
| |
| invoke_on_all_policies(f, in.begin(), out.begin()); |
| } |
| |
| template <typename Policy, typename F> |
| static void |
| invoke_if(Policy&&, F f) |
| { |
| #if _PSTL_ICC_16_VC14_TEST_SIMD_LAMBDA_DEBUG_32_BROKEN || _PSTL_ICC_17_VC141_TEST_SIMD_LAMBDA_DEBUG_32_BROKEN |
| __pstl::__internal::invoke_if_not(__pstl::__internal::allow_unsequenced<Policy>(), f); |
| #else |
| f(); |
| #endif |
| } |
| |
| } /* namespace TestUtils */ |