Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ set(HEADER_FILES
include/poolstl/execution
include/poolstl/numeric
include/poolstl/seq_fwd.hpp
include/poolstl/iota_iter.hpp
include/poolstl/internal/utils.hpp
include/poolstl/internal/ttp_impl.hpp
include/poolstl/internal/task_thread_pool.hpp)
Expand Down
96 changes: 96 additions & 0 deletions include/poolstl/iota_iter.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright (C) 2023 Adam Lugowski. All rights reserved.
// Use of this source code is governed by:
// the BSD 2-clause license, the MIT license, or at your choosing the BSL-1.0 license found in the LICENSE.*.txt files.
// SPDX-License-Identifier: BSD-2-Clause OR MIT OR BSL-1.0

#ifndef POOLSTL_IOTA_ITER_HPP
#define POOLSTL_IOTA_ITER_HPP

#include <cstddef>
#include <iterator>

namespace poolstl {

/**
* An iterator over the integers.
*
* Effectively a view on a fictional vector populated by std::iota, but without materializing anything.
*
* Useful to parallelize loops that are not over a container, like this:
*
* \code{.cpp}
* for (int i = 0; i < 10; ++i) {
* }
*\endcode
*
* Becomes:
* \code{.cpp}
* std::for_each(iota_iter<int>(0), iota_iter<int>(10), [](int i) {
* });
* \endcode
*
* @tparam T A type that acts as an integer.
*/
template<typename T>
class iota_iter {
public:
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T *;
using reference = T &;
using iterator_category = std::random_access_iterator_tag;

iota_iter() : value{} {}
explicit iota_iter(T rhs) : value(rhs) {}
iota_iter(const iota_iter<T> &rhs) : value(rhs.value) {}

iota_iter<T> &operator=(T rhs) { value = rhs; return *this; }
iota_iter<T> &operator=(const iota_iter &rhs) { value = rhs.value; return *this; }

T operator*() const { return value; }
T operator[](difference_type rhs) const { return value + rhs; }
// operator-> has no meaning in this application

bool operator==(const iota_iter<T> &rhs) const { return value == rhs.value; }
bool operator!=(const iota_iter<T> &rhs) const { return value != rhs.value; }
bool operator<(const iota_iter<T> &rhs) const { return value < rhs.value; }
bool operator>(const iota_iter<T> &rhs) const { return value > rhs.value; }
bool operator<=(const iota_iter<T> &rhs) const { return value <= rhs.value; }
bool operator>=(const iota_iter<T> &rhs) const { return value >= rhs.value; }

iota_iter<T> &operator+=(difference_type rhs) { value += rhs; return *this; }
iota_iter<T> &operator-=(difference_type rhs) { value -= rhs; return *this; }

iota_iter<T> &operator++() { ++value; return *this; }
iota_iter<T> &operator--() { --value; return *this; }
iota_iter<T> operator++(int) { iota_iter<T> ret(value); ++value; return ret; }
iota_iter<T> operator--(int) { iota_iter<T> ret(value); --value; return ret; }

difference_type operator-(const iota_iter<T> &rhs) const { return value - rhs.value; }
iota_iter<T> operator-(difference_type rhs) const { return iota_iter(value - rhs); }
iota_iter<T> operator+(difference_type rhs) const { return iota_iter(value + rhs); }

friend inline iota_iter<T> operator+(difference_type lhs, const iota_iter<T> &rhs) {
return iota_iter(lhs + rhs.value);
}

protected:
T value;
};
}

namespace std {
/**
* Specialize std::iterator_traits for poolstl::iota_iter.
*/
template <typename T>
struct iterator_traits<poolstl::iota_iter<T>> {
using value_type = typename poolstl::iota_iter<T>::value_type;
using difference_type = typename poolstl::iota_iter<T>::difference_type;
using pointer = typename poolstl::iota_iter<T>::pointer;
using reference = typename poolstl::iota_iter<T>::reference;
using iterator_category = typename poolstl::iota_iter<T>::iterator_category;
};
}

#endif
3 changes: 3 additions & 0 deletions include/poolstl/poolstl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
#include "numeric"
#include "seq_fwd.hpp"

// Note that iota_iter.hpp is self-contained in its own right.
#include "iota_iter.hpp"

/*
* Optionally alias `poolstl::par` as `std::execution::par` to enable poolSTL to fill in for missing compiler support.
*
Expand Down
116 changes: 116 additions & 0 deletions tests/poolstl_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@
#include "utils.hpp"

namespace ttp = task_thread_pool;
using poolstl::iota_iter;


namespace Catch {
template<>
struct StringMaker<iota_iter<long>> {
static std::string convert( iota_iter<long> const& value ) {
return std::to_string(*value);
}
};
}

TEST_CASE("any_all_none", "[alg][algorithm]") {
for (auto num_threads : test_thread_counts) {
Expand Down Expand Up @@ -398,4 +409,109 @@ TEST_CASE("execution_policies", "[execution]") {
REQUIRE(1 == std::count(poolstl::par_if<>(true).on(pool), v.cbegin(), v.cend(), 5));
}

TEST_CASE("iota_iter(use)", "[iterator]") {
REQUIRE(15 == std::reduce(poolstl::par, iota_iter<int>(0), iota_iter<int>(6)));
REQUIRE(1 == std::count(poolstl::par, iota_iter<int>(0), iota_iter<int>(6), 5));
REQUIRE(1 == std::count(iota_iter<int>(0), iota_iter<int>(6), 5));
}

TEST_CASE("iota_iter(def)", "[iterator]") {
// Test that iota_iter meets RandomAccessIterator requirements.
// See https://en.cppreference.com/w/cpp/iterator/random_access_iterator

long a_init = 5;
long b_init = 10;
iota_iter<long> a(a_init);
iota_iter<long> b(b_init);
auto n = std::distance(a, b);
REQUIRE(n == 5);

// (a += n) is equal to b.
REQUIRE((a += n) == b);
a = a_init; b = b_init;

// std::addressof(a += n) is equal to std::addressof(a). [1]
REQUIRE(std::addressof(a += n) == std::addressof(a));

// (a + n) is equal to (a += n).
{
auto lhs = (a + n);
REQUIRE(lhs == (a += n));
a = a_init; b = b_init;
}

// (a + n) is equal to (n + a).
REQUIRE((a + n) == (n + a));

// For any two positive integers x and y, if a + (x + y) is valid, then a + (x + y) is equal to (a + x) + y.
int x = 12, y = 55;
REQUIRE((a + (x + y)) == ((a + x) + y));

// a + 0 is equal to a.
REQUIRE((a + 0) == a);

// If (a + (n - 1)) is valid, then --b is equal to (a + (n - 1)).
REQUIRE(--b == (a + (n - 1)));
a = a_init; b = b_init;

// (b += -n) and (b -= n) are both equal to a.
REQUIRE((b += -n) == a);
a = a_init; b = b_init;

REQUIRE((b -= n) == a);
a = a_init; b = b_init;

// std::addressof(b -= n) is equal to std::addressof(b). [1]
REQUIRE(std::addressof(b -= n) == std::addressof(b));
a = a_init; b = b_init;

// (b - n) is equal to (b -= n).
{
auto lhs = (b - n);
REQUIRE(lhs == (b -= n));
a = a_init; b = b_init;
}

// If b is dereferenceable, then a[n] is valid and is equal to *b.
REQUIRE(a[n] == *b);

// bool(a <= b) is true.
REQUIRE(bool(a <= b));


// exercise the other methods
REQUIRE(a == a);
REQUIRE(a != b);
REQUIRE(a < b);
REQUIRE(b > a);
REQUIRE(b >= a);

REQUIRE(std::addressof(++a) == std::addressof(a));
REQUIRE(std::addressof(--a) == std::addressof(a));

{
auto lhs = a;
REQUIRE(lhs == a++);
}

{
auto lhs = (a + 1);
REQUIRE(lhs == ++a);
}

{
auto lhs = a;
REQUIRE(lhs == a--);
}

{
auto lhs = (a - 1);
REQUIRE(lhs == --a);
}

// default constructible
iota_iter<long> c;
REQUIRE(*c == 0);
}

std::mt19937 rng{1};