Skip to content

Commit

Permalink
constexpr algorithms
Browse files Browse the repository at this point in the history
  • Loading branch information
alandefreitas committed Apr 1, 2022
1 parent ef3ab1f commit 326957b
Show file tree
Hide file tree
Showing 27 changed files with 1,789 additions and 1,158 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Futures

> C++ Lock-Free Task Programming with Asio Executors
> C++ Task Programming
[![Futures](docs/img/futures_banner.png)](https://alandefreitas.github.io/futures/)

Expand Down
3 changes: 2 additions & 1 deletion docs/.doxybook/templates/merged_overload_details.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ template <{% for param in overload.templateParams %}{{param.typePlain}}{{param.n
{# Render return type -#}
{% if existsIn(overload, "typePlain") -%}
{%- if overload.typePlain in ["__implementation_defined__", "auto", "decltype(auto)"] -%}
/* see below */{% else -%}
/* see below */{% else if overload.typePlain == "decltype(auto) constexpr" -%}
constexpr /* see below */{% else -%}
{{- overload.typePlain -}}
{%- endif %} {% endif %}
{# Render function name -#}
Expand Down
1 change: 1 addition & 0 deletions docs/Doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ MACRO_EXPANSION=YES
EXPAND_ONLY_PREDEF=YES
PREDEFINED= FUTURES_DOXYGEN \
FUTURES_CONSTEXPR=constexpr \
FUTURES_CONSTANT_EVALUATED_CONSTEXPR=constexpr \
FUTURES_USE_ASIO=1

JAVADOC_AUTOBRIEF=YES
Expand Down
42 changes: 29 additions & 13 deletions docs/algorithms/algorithms.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,48 @@
# Algorithms

The header [`algorithm.h`](/futures/reference/Files/algorithm_8h.md) includes implementations of common STL algorithms using the
library primitives.
The header [`algorithm.h`](/futures/reference/Files/algorithm_8h.md) and the corresponding
[Algorithms Module](reference/Modules/group__algorithms/) includes parallel implementations of common STL algorithms
using the library primitives.

{{ code_snippet("algorithm/algorithms.cpp", "algorithm") }}

Like the C++20 [ranges library](https://en.cppreference.com/w/cpp/ranges), these algorithms accept both iterators or
ranges as parameters.

Unless a policy is explicitly stated, all algorithms are parallel by default. These algorithms give us access to
parallel algorithms that rely only on executors. This allows us to avoid of more complex libraries, such
as [TBB](https://github.com/oneapi-src/oneTBB), to execute efficient parallel algorithms.
{{ code_snippet("algorithm/algorithms.cpp", "algorithm_range") }}

## Executors

Like other parallel functions defined in this library, these algorithms allow simple execution policies to be replaced
by concrete executors.

{{ code_snippet("algorithm/algorithms.cpp", "executor") }}

!!! warning "Parallel by default"
## Parallel by default

Unless an alternative policy or [executor] is provided, all algorithms are executed in parallel by default whenever it
is "reasonably safe" to do so. A parallel algorithm is considered "reasonably safe" if there are no implicit data races
and deadlocks in its provided functors.

To execute algorithms sequentially, an appropriate executor or policy should be provided:

{{ code_snippet("algorithm/algorithms.cpp", "inline_executor") }}

{{ code_snippet("algorithm/algorithms.cpp", "seq_policy") }}

Unless a policy is explicitly stated, all algorithms are parallel by default. These algorithms give us access to
parallel algorithms that rely only on executors. This allows us to avoid of more complex libraries, such
as [TBB](https://github.com/oneapi-src/oneTBB), to execute efficient parallel algorithms.

## Compile time algorithms

Unless an alternative policy or [executor] is provided, all algorithms are executed in parallel by default whenever
it is "reasonably safe" to do so. A parallel algorithms is considered "reasonably safe" if there are no implicit
data races and deadlocks in its provided functors.
Like in [C++20](https://en.cppreference.com/w/cpp/algorithm), these algorithms can also be used in
`constexpr` contexts with the default inline executor for these contexts.

!!! hint "[constexpr]"
{{ code_snippet("algorithm/algorithms.cpp", "constexpr") }}

Unlike [C++ algorithms](https://en.cppreference.com/w/cpp/algorithm), async algorithms using runtime executors and
schedulers cannot be executed as [constexpr]. However, in C++20, algorithms might still be declared [constexpr] and
make use of [std::is_constant_evaluated].
This feature depends on an internal library implementation equivalent to [std::is_constant_evaluated]. This
implementation is available in most compilers (MSVC 1925, GCC 6, Clang 9), even when C++20 is not available. The macro
`FUTURES_HAS_CONSTANT_EVALUATED` can be used to identify if the feature is available.

--8<-- "docs/references.md"
45 changes: 41 additions & 4 deletions examples/algorithm/algorithms.cpp
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
#include <futures/algorithm.hpp>
#include <futures/futures.hpp>
#include <array>
#include <iostream>
#include <numeric>
#include <vector>

int
main() {
using namespace futures;

//[algorithm Parallel Algorithms
std::vector<int> v(50000);
std::iota(v.begin(), v.end(), 1);

int c = futures::reduce(v, 0); // parallel by default
std::cout << "Sum: " << c << '\n';
//]
{
//[algorithm Parallel Algorithms
int c = futures::reduce(v.begin(), v.end(), 0); // parallel by default
std::cout << "Sum: " << c << '\n';
//]
}

{
//[algorithm_range Ranges
int c = futures::reduce(v, 0); // parallel by default
std::cout << "Sum: " << c << '\n';
//]
}

//[executor Custom executor
asio::thread_pool pool(4);
Expand All @@ -22,11 +34,36 @@ main() {
});
//]

{
//[seq_policy Execution policy
int c = futures::reduce(futures::seq, v, 0); // sequential execution
std::cout << "Sum: " << c << '\n';
//]
}

{
//[inline_executor Inline executor
int c = futures::reduce(make_inline_executor(), v, 0); // sequential
// execution
std::cout << "Sum: " << c << '\n';
//]
}

{
//[constexpr Compile-time algorithms
constexpr std::array<int, 5> a = { 1, 2, 3, 4, 5 };
constexpr int n = futures::reduce(a);
constexpr std::array<int, n> b{};
std::cout << "n: " << b.size() << '\n';
//]
}

//[partitioner Defining a custom partitioner
auto p = [](auto first, auto last) {
return std::next(first, (last - first) / 2);
};
//]

//[partitioner_algorithm Using a custom partitioner
auto it = find(ex, p, v, 3000);
if (it != v.end()) {
Expand Down
38 changes: 36 additions & 2 deletions include/futures/algorithm/all_of.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,32 @@ namespace futures {
tasks_{};
};

template <
class I,
class S,
class Fun
#ifndef FUTURES_DOXYGEN
,
std::enable_if_t<
// clang-format off
is_input_iterator_v<I> &&
is_sentinel_for_v<S, I> &&
is_indirectly_unary_invocable_v<Fun, I> &&
std::is_copy_constructible_v<Fun>
// clang-format on
,
int> = 0
#endif
>
static FUTURES_CONSTANT_EVALUATED_CONSTEXPR bool
inline_all_of(I first, S last, Fun p) {
for (; first != last; ++first) {
if (!p(*first)) {
return false;
}
}
return true;
}

/// Complete overload of the all_of algorithm
/// @tparam E Executor type
Expand Down Expand Up @@ -145,9 +171,17 @@ namespace futures {
int> = 0
#endif
>
bool
FUTURES_CONSTANT_EVALUATED_CONSTEXPR bool
run(const E &ex, P p, I first, S last, Fun f) const {
return all_of_graph<E>(ex).all_of(p, first, last, f);
if constexpr (std::is_same_v<std::decay_t<E>, inline_executor>) {
return inline_all_of(first, last, f);
} else {
if (detail::is_constant_evaluated()) {
return inline_all_of(first, last, f);
} else {
return all_of_graph<E>(ex).all_of(p, first, last, f);
}
}
}
};

Expand Down
39 changes: 37 additions & 2 deletions include/futures/algorithm/any_of.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,33 @@ namespace futures {
tasks_{};
};

template <
class I,
class S,
class Fun
#ifndef FUTURES_DOXYGEN
,
std::enable_if_t<
// clang-format off
is_input_iterator_v<I> &&
is_sentinel_for_v<S, I> &&
is_indirectly_unary_invocable_v<Fun, I> &&
std::is_copy_constructible_v<Fun>
// clang-format on
,
int> = 0
#endif
>
static FUTURES_CONSTANT_EVALUATED_CONSTEXPR bool
inline_any_of(I first, S last, Fun p) {
for (; first != last; ++first) {
if (p(*first)) {
return true;
}
}
return false;
}

/// Complete overload of the any_of algorithm
/// @tparam E Executor type
/// @tparam P Partitioner type
Expand Down Expand Up @@ -137,9 +164,17 @@ namespace futures {
int> = 0
#endif
>
bool
FUTURES_CONSTANT_EVALUATED_CONSTEXPR bool
run(const E &ex, P p, I first, S last, Fun f) const {
return any_of_graph<E>(ex).any_of(p, first, last, f);
if constexpr (std::is_same_v<std::decay_t<E>, inline_executor>) {
return inline_any_of(first, last, f);
} else {
if (detail::is_constant_evaluated()) {
return inline_any_of(first, last, f);
} else {
return any_of_graph<E>(ex).any_of(p, first, last, f);
}
}
}
};

Expand Down
48 changes: 42 additions & 6 deletions include/futures/algorithm/count.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,34 @@ namespace futures {
{
friend value_cmp_algorithm_functor<count_functor>;

template <
class I,
class S,
class T
#ifndef FUTURES_DOXYGEN
,
std::enable_if_t<
// clang-format off
is_input_iterator_v<I> &&
is_sentinel_for_v<S, I> &&
is_indirectly_binary_invocable_v<equal_to, T *, I>
// clang-format on
,
int> = 0
#endif
>
static FUTURES_CONSTANT_EVALUATED_CONSTEXPR iter_difference_t<I>
inline_count(I first, S last, const T &v) {
iter_difference_t<I> ret = 0;
for (; first != last; ++first) {
if (*first == v) {
ret++;
}
}
return ret;
}


/// Complete overload of the count algorithm
/// @tparam E Executor type
/// @tparam P Partitioner type
Expand Down Expand Up @@ -67,12 +95,20 @@ namespace futures {
int> = 0
#endif
>
iter_difference_t<I>
run(const E &ex, P p, I first, S last, T v) const {
return count_if_functor::count_if_graph<E, I>(ex)
.count_if(p, first, last, [&v](const auto &el) {
return el == v;
});
FUTURES_CONSTANT_EVALUATED_CONSTEXPR iter_difference_t<I>
run(const E &ex, P p, I first, S last, const T &v) const {
if constexpr (std::is_same_v<std::decay_t<E>, inline_executor>) {
return inline_count(first, last, v);
} else {
if (detail::is_constant_evaluated()) {
return inline_count(first, last, v);
} else {
return count_if_functor::count_if_graph<E, I>(ex)
.count_if(p, first, last, [&v](const auto &el) {
return el == v;
});
}
}
}
};

Expand Down
41 changes: 39 additions & 2 deletions include/futures/algorithm/count_if.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,35 @@ namespace futures {
tasks_{};
};

template <
class I,
class S,
class Fun
#ifndef FUTURES_DOXYGEN
,
std::enable_if_t<
// clang-format off
is_input_iterator_v<I> &&
is_sentinel_for_v<S, I> &&
is_indirectly_unary_invocable_v<Fun, I> &&
std::is_copy_constructible_v<Fun>
// clang-format on
,
int> = 0
#endif
>
static FUTURES_CONSTANT_EVALUATED_CONSTEXPR iter_difference_t<I>
inline_count_if(I first, S last, Fun p) {
iter_difference_t<I> ret = 0;
for (; first != last; ++first) {
if (p(*first)) {
ret++;
}
}
return ret;
}


/// Complete overload of the count_if algorithm
/// @tparam E Executor type
/// @tparam P Partitioner type
Expand Down Expand Up @@ -135,9 +164,17 @@ namespace futures {
int> = 0
#endif
>
iter_difference_t<I>
FUTURES_CONSTANT_EVALUATED_CONSTEXPR iter_difference_t<I>
run(const E &ex, P p, I first, S last, Fun f) const {
return count_if_graph<E, I>(ex).count_if(p, first, last, f);
if constexpr (std::is_same_v<std::decay_t<E>, inline_executor>) {
return inline_count_if(first, last, f);
} else {
if (detail::is_constant_evaluated()) {
return inline_count_if(first, last, f);
} else {
return count_if_graph<E, I>(ex).count_if(p, first, last, f);
}
}
}
};

Expand Down

0 comments on commit 326957b

Please sign in to comment.