Skip to content

Commit e78f3ca

Browse files
[V4] Projected (#108)
* projected projected * naming in for each * spell * document indirect invocations concepts * projected comments * spell * strip indirect value type tmp strip
1 parent a88acb7 commit e78f3ca

6 files changed

Lines changed: 203 additions & 108 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ target_sources(libfork_libfork
8989
src/core/awaitables.cxx
9090
src/core/promise.cxx
9191
src/core/stop.cxx
92+
src/core/projected.cxx
9293
# libfork.batteries
9394
src/batteries/batteries.cxx
9495
src/batteries/deque.cxx

src/algorithm/for_each.cxx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ namespace lf {
1111
template <typename T>
1212
concept sized_random_access_range = std::ranges::random_access_range<T> && std::ranges::sized_range<T>;
1313

14+
// TODO: relax the constraints?
15+
//
16+
// consider a projection if it makes the concept unifyable with fold etc
17+
1418
struct for_each_impl {
1519
private:
1620
template <worker_context Context>
@@ -36,7 +40,7 @@ struct for_each_impl {
3640
LF_ASSUME(len >= 0);
3741

3842
if (len <= n) {
39-
if constexpr (indirectly_regular_unary_async_invocable<Fn, Context, I>) {
43+
if constexpr (indirectly_async_regular_unary_invocable<Fn, Context, I>) {
4044
// Prefer async
4145
auto sc = co_await scope();
4246
for (; head != tail; ++head) {
@@ -71,7 +75,7 @@ struct for_each_impl {
7175
case 0:
7276
co_return;
7377
case 1:
74-
if constexpr (indirectly_regular_unary_async_invocable<Fn, Context, I>) {
78+
if constexpr (indirectly_async_regular_unary_invocable<Fn, Context, I>) {
7579
auto sc = co_await scope();
7680
co_await sc.call_drop(fn, *head);
7781
co_await sc.join();
@@ -81,6 +85,10 @@ struct for_each_impl {
8185
co_return;
8286
}
8387

88+
// TODO: round mid-point up
89+
// Special case for (case 2/3) for async invocables to avoid the redundant task
90+
// Benchmarks for ^ to see if it matters
91+
8492
auto mid = head + (len / 2);
8593
auto sc = co_await scope();
8694
co_await sc.fork(for_each_impl{}, head, mid, fn);

src/core/concepts/indirect.cxx

Lines changed: 75 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,73 @@
1-
21
export module libfork.core:concepts_indirect;
32

43
import std;
54

6-
import libfork.utils;
7-
85
import :concepts_invocable;
9-
10-
/**
11-
* The purpose of this file is to define async versions of:
12-
*
13-
* `indirectly_unary_invocable`
14-
*
15-
* This requires `indirect-value-t` which is in turn requires an async version
16-
* of `projected` which in turns requires an async version of
17-
* `indirect_result_t`
18-
*
19-
*/
6+
import :concepts_context;
207

218
namespace lf {
229

23-
// ========= Forward decl =========
10+
// A type can derive from this to opt-into indirect-value-t customization
11+
struct indirect_value_customization {};
2412

25-
template <typename T>
13+
template <typename I>
2614
struct indirect_value {
27-
using type = std::iter_value_t<T> &;
15+
using type = std::iter_value_t<I> &;
2816
};
2917

18+
// strip cv-ref qualifiers
3019
template <typename T>
31-
using indirect_value_t = indirect_value<T>::type;
20+
requires (!std::same_as<T, std::remove_cvref_t<T>>)
21+
struct indirect_value<T> : indirect_value<std::remove_cvref_t<T>> {};
22+
23+
// Specialization for types that customize
24+
template <std::derived_from<indirect_value_customization> T>
25+
requires std::same_as<T, std::remove_cvref_t<T>>
26+
struct indirect_value<T> {
27+
using type = T::indirect_value_type;
28+
};
29+
30+
template <typename I>
31+
using indirect_value_t = indirect_value<I>::type;
3232

3333
// ========= Core concepts =========
3434

35+
/**
36+
* @brief A version of `std::indirectly_unary_invocable` that supports
37+
* libfork's projection type.
38+
*/
39+
export template <typename Fn, typename I>
40+
concept indirectly_sync_unary_invocable = //
41+
std::indirectly_readable<I> && //
42+
std::copy_constructible<Fn> && //
43+
std::invocable<Fn &, indirect_value_t<I>> && //
44+
std::invocable<Fn &, std::iter_reference_t<I>> && //
45+
std::common_reference_with< //
46+
std::invoke_result_t<Fn &, indirect_value_t<I>>, //
47+
std::invoke_result_t<Fn &, std::iter_reference_t<I>> //
48+
>; //
49+
50+
/**
51+
* @brief A version of `std::indirectly_regular_unary_invocable` that supports
52+
* libfork's projection type.
53+
*/
54+
export template <typename Fn, typename I>
55+
concept indirectly_sync_regular_unary_invocable = //
56+
std::indirectly_readable<I> && //
57+
std::copy_constructible<Fn> && //
58+
std::regular_invocable<Fn &, indirect_value_t<I>> && //
59+
std::regular_invocable<Fn &, std::iter_reference_t<I>> && //
60+
std::common_reference_with< //
61+
std::invoke_result_t<Fn &, indirect_value_t<I>>, //
62+
std::invoke_result_t<Fn &, std::iter_reference_t<I>> //
63+
>; //
64+
65+
/**
66+
* @brief A variant of `std::indirectly_unary_invocable` that supports
67+
* libfork's projection type and requires an async invocable.
68+
*/
3569
export template <typename Fn, typename Context, typename I>
36-
concept indirectly_unary_async_invocable = //
70+
concept indirectly_async_unary_invocable = //
3771
worker_context<Context> && //
3872
std::indirectly_readable<I> && //
3973
std::copy_constructible<Fn> && //
@@ -44,8 +78,12 @@ concept indirectly_unary_async_invocable = //
4478
async_result_t<Fn &, Context, std::iter_reference_t<I>> //
4579
>; //
4680

81+
/**
82+
* @brief A variant of `std::indirectly_regular_unary_invocable` that supports
83+
* libfork's projection type and requires an async invocable.
84+
*/
4785
export template <typename Fn, typename Context, typename I>
48-
concept indirectly_regular_unary_async_invocable =
86+
concept indirectly_async_regular_unary_invocable = //
4987
worker_context<Context> && //
5088
std::indirectly_readable<I> && //
5189
std::copy_constructible<Fn> && //
@@ -56,77 +94,30 @@ concept indirectly_regular_unary_async_invocable =
5694
async_result_t<Fn &, Context, std::iter_reference_t<I>> //
5795
>; //
5896

59-
export template <typename Fn, typename Context, typename I>
60-
concept indirectly_unary_invocable =
61-
indirectly_unary_async_invocable<Fn, Context, I> || std::indirectly_unary_invocable<Fn, I>;
62-
63-
export template <typename Fn, typename Context, typename I>
64-
concept indirectly_regular_unary_invocable = indirectly_regular_unary_async_invocable<Fn, Context, I> ||
65-
std::indirectly_regular_unary_invocable<Fn, I>;
66-
67-
// ========= indirect_result =========
68-
6997
/**
70-
* @brief A version of `std::invoke_result` that supports both regular invocables and async invocables.
98+
* @brief A variant of `std::indirectly_unary_invocable` that supports either
99+
* sync or async invocables.
71100
*
72-
* This gives preference to async invocation.
101+
* In general if a function is both sync and async invocable it is expected
102+
* that the async version will be preferred.
73103
*/
74-
template <typename Proj, typename Context, typename... Args>
75-
struct invoke_result : std::invoke_result<Proj, Args...> {};
76-
77-
// More constrained so should be selected if both regular and async invocations are possible.
78-
template <typename Proj, typename Context, typename... Args>
79-
requires async_invocable<Proj, Context, Args...>
80-
struct invoke_result<Proj, Context, Args...> {
81-
using type = async_result_t<Proj, Context, Args...>;
82-
};
83-
84-
template <class F, typename Context, typename... Args>
85-
using invoke_result_t = invoke_result<F, Context, Args...>::type;
86-
87-
template <class F, typename Context, std::indirectly_readable... Is>
88-
using indirect_result_t = invoke_result<F, Context, std::iter_reference_t<Is>...>::type;
89-
90-
// ========= Projected =========
91-
92-
// C++26 ADL firewalled implementation.
93-
94-
struct hidden_projected_base {};
95-
96-
template <bool WeaklyIncrementable, typename I, typename Proj, typename Context>
97-
struct projected_impl {
98-
99-
static_assert(!WeaklyIncrementable, "Should hit specialization for weakly incrementable");
100-
101-
struct type : hidden_projected_base {
102-
103-
// Used by indirect_value
104-
using hidden_indirect_value = invoke_result<Proj &, Context, indirect_value_t<I>>;
105-
106-
using value_type = std::remove_cvref_t<indirect_result_t<Proj &, Context, I>>;
107-
auto operator*() const -> indirect_result_t<Proj &, Context, I>;
108-
};
109-
};
104+
export template <typename Fn, typename Context, typename I>
105+
concept indirectly_unary_invocable =
106+
indirectly_async_unary_invocable<Fn, Context, I> || indirectly_sync_unary_invocable<Fn, I>;
110107

111-
template <std::weakly_incrementable I, typename Proj, typename Context>
112-
struct projected_impl<true, I, Proj, Context> {
113-
struct type : projected_impl<false, I, Proj, Context>::type {
114-
using difference_type = std::iter_difference_t<I>;
115-
};
116-
};
108+
// clang-format off
117109

118110
/**
119-
* @brief A version of `std::projected` that supports both regular invocables and async invocables.
111+
* @brief A variant of `std::indirectly_regular_unary_invocable` that supports
112+
* either sync or async invocables.
120113
*
121-
* Note: this mandates regularly invocable projections.
114+
* In general if a function is both sync and async invocable it is expected
115+
* that the async version will be preferred.
122116
*/
123-
export template <std::indirectly_readable I, typename Proj, worker_context Context>
124-
requires indirectly_regular_unary_invocable<Proj, Context, I>
125-
using projected = projected_impl<std::weakly_incrementable<I>, I, Proj, Context>::type;
126-
127-
// Specialization of indirect_value
128-
template <typename P>
129-
requires std::derived_from<P, hidden_projected_base>
130-
struct indirect_value<P> : P::hidden_indirect_value {};
117+
export template <typename Fn, typename Context, typename I>
118+
concept indirectly_regular_unary_invocable =
119+
indirectly_async_regular_unary_invocable<Fn, Context, I> || indirectly_sync_regular_unary_invocable<Fn, I>;
120+
121+
// clang-format on
131122

132123
} // namespace lf

src/core/core.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ export import :stop;
2121
export import :exception;
2222
export import :final_suspend;
2323
export import :awaitables;
24+
export import :projected;

src/core/projected.cxx

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
export module libfork.core:projected;
2+
3+
import std;
4+
5+
import :concepts_invocable;
6+
import :concepts_context;
7+
import :concepts_indirect;
8+
9+
namespace lf {
10+
11+
template <typename I>
12+
struct conditional_difference_type : indirect_value_customization {};
13+
14+
template <std::weakly_incrementable I>
15+
struct conditional_difference_type<I> : indirect_value_customization {
16+
using difference_type = std::iter_difference_t<I>;
17+
};
18+
19+
// C++26 ADL firewalled implementation.
20+
template <typename I, typename Fn, typename Context>
21+
struct projected_impl;
22+
23+
// sync
24+
template <typename I, typename Fn, typename>
25+
struct projected_impl {
26+
struct type : conditional_difference_type<I> {
27+
private:
28+
friend struct indirect_value<type>;
29+
using indirect_value_type = std::invoke_result_t<Fn &, indirect_value_t<I>>;
30+
31+
using reference_type = std::invoke_result_t<Fn &, std::iter_reference_t<I>>;
32+
33+
public:
34+
using value_type = std::remove_cvref_t<reference_type>;
35+
auto operator*() const -> reference_type;
36+
};
37+
};
38+
39+
// async
40+
template <typename I, typename Fn, typename Context>
41+
requires indirectly_async_unary_invocable<Fn, Context, I>
42+
struct projected_impl<I, Fn, Context> {
43+
struct type : conditional_difference_type<I> {
44+
private:
45+
friend struct indirect_value<type>;
46+
using indirect_value_type = async_result_t<Fn &, Context, indirect_value_t<I>>;
47+
48+
using reference_type = async_result_t<Fn &, Context, std::iter_reference_t<I>>;
49+
50+
public:
51+
using value_type = std::remove_cvref_t<reference_type>;
52+
auto operator*() const -> reference_type;
53+
};
54+
};
55+
56+
template <typename Fn, typename Context, typename... T>
57+
concept async_defaultable =
58+
((async_invocable<Fn, Context, T> && std::default_initializable<async_result_t<Fn, Context, T>>) && ...);
59+
60+
template <typename Fn, typename Context, typename I>
61+
concept async_defaultable_impl =
62+
async_defaultable<Fn, Context, std::iter_reference_t<I>, indirect_value_t<I>>;
63+
64+
template <typename Fn, typename Context, typename I>
65+
concept indirect_async_defaultable =
66+
!indirectly_async_regular_unary_invocable<Fn, Context, I> || async_defaultable_impl<Fn, Context, I>;
67+
68+
/**
69+
* @brief Test if `I` can be projected through `Fn` in the context of `Context`
70+
*
71+
* This requires the standard indirectly regular invocable and, in addition,
72+
* async projections must return a default initializable type.
73+
*
74+
* Projectable && indirectly_async_unary_invocable implies an async projection
75+
* with default initializable async result type.
76+
*/
77+
export template <typename Fn, typename Context, typename I>
78+
concept projectable =
79+
indirectly_regular_unary_invocable<Fn, Context, I> && indirect_async_defaultable<Fn, Context, I>;
80+
81+
/**
82+
* @brief A version of `std::projected` that supports both regular invocables and async invocables.
83+
*/
84+
export template <worker_context Context, std::indirectly_readable I, projectable<Context, I> Fn>
85+
using projected = projected_impl<I, Fn, Context>::type;
86+
87+
} // namespace lf

0 commit comments

Comments
 (0)