Skip to content

Commit dffd43f

Browse files
committed
algorithm: Add lambda helper function
1 parent 58f6b96 commit dffd43f

File tree

5 files changed

+178
-0
lines changed

5 files changed

+178
-0
lines changed

docs/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ custom_target('build-doc',
1919
'src/headers/algorithm/sequence.md',
2020
'src/headers/algorithm/transform.md',
2121
'src/headers/algorithm/when_all.md',
22+
'src/headers/algorithm/lambda.md',
2223
'src/headers/basic.md',
2324
'src/headers/basic/any_receiver.md',
2425
'src/headers/basic/detached.md',

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
- [let](headers/algorithm/let.md)
1616
- [sequence](headers/algorithm/sequence.md)
1717
- [when\_all](headers/algorithm/when_all.md)
18+
- [lambda](headers/algorithm/lambda.md)
1819
- [async/basic.hpp](headers/basic.md)
1920
- [co\_awaits\_to](headers/basic/co_awaits_to.md)
2021
- [sender\_awaiter](headers/basic/sender_awaiter.md)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# lambda
2+
3+
`lambda` is a helper function which wraps a function-like object to
4+
extend it's lifetime for the duration of the async operation. Primarily
5+
intended to be used with lambdas with captures as arguments to
6+
`race_and_cancel` and such.
7+
8+
The returned wrapper is callable with the same arguments as the
9+
original function object, and the arguments get forwarded to it.
10+
11+
## Prototype
12+
13+
```cpp
14+
template <typename Fn>
15+
lambda_callable sequence(Fn fn);
16+
```
17+
18+
### Requirements
19+
20+
`Fn` is a function-like object that produces a sender.
21+
22+
### Arguments
23+
24+
- `fn` - the function-like object to wrap.
25+
26+
### Return value
27+
28+
This function returns a wrapper object that upon being called returns
29+
a sender of unspecified type. The sender forwards the return value
30+
from the sender returned by `fn`.
31+
32+
## Examples
33+
34+
```cpp
35+
36+
auto x = std::make_shared<int>(1);
37+
38+
async::run(
39+
async::race_and_cancel(
40+
async::lambda(
41+
[x_ = std::make_shared<int>(1)] (async::cancellation_token ct)
42+
-> async::result<void> {
43+
// x_ is valid here
44+
std::cout << *x_ << "\n";
45+
co_return;
46+
/* ... */
47+
}
48+
),
49+
[x_ = std::make_shared<int>(2)] (async::cancellation_token ct)
50+
-> async::result<void> {
51+
// x_ is NOT valid here
52+
// the hidden lambda `this` is out of scope!
53+
// std::cout << *x_ << "\n"; // crash !
54+
co_return;
55+
}
56+
)
57+
);
58+
```
59+
60+
Output:
61+
```
62+
1
63+
```

include/async/algorithm.hpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,4 +797,59 @@ operator co_await(when_all_sender<Senders...> s) {
797797
return {std::move(s)};
798798
}
799799

800+
//---------------------------------------------------------------------------------------
801+
// lambda()
802+
//---------------------------------------------------------------------------------------
803+
804+
template <typename R, typename Fn, typename ...Args>
805+
struct lambda_operation {
806+
lambda_operation(R receiver, Fn fn, std::tuple<Args...> args)
807+
: fn_{std::move(fn)}, op_{execution::connect(std::apply(fn, args), std::move(receiver))} { }
808+
809+
bool start_inline() {
810+
return execution::start_inline(op_);
811+
}
812+
813+
Fn fn_;
814+
execution::operation_t<std::invoke_result_t<Fn, Args...>, R> op_;
815+
};
816+
817+
template <typename Fn, typename ...Args>
818+
struct [[nodiscard]] lambda_sender {
819+
using value_type = std::invoke_result_t<Fn, Args...>::value_type;
820+
821+
Fn fn;
822+
std::tuple<Args...> args;
823+
824+
template<typename Receiver>
825+
friend lambda_operation<Receiver, Fn, Args...>
826+
connect(lambda_sender s, Receiver r) {
827+
return {std::move(r), std::move(s.fn), std::move(s.args)};
828+
}
829+
};
830+
831+
template <typename Fn, typename ...Args>
832+
sender_awaiter<lambda_sender<Fn, Args...>, typename lambda_sender<Fn, Args...>::value_type>
833+
operator co_await(lambda_sender<Fn, Args...> s) {
834+
return {std::move(s)};
835+
}
836+
837+
template <typename Fn>
838+
struct [[nodiscard]] lambda_callable {
839+
template <typename ...Args>
840+
auto operator()(Args &&...args) {
841+
return lambda_sender{
842+
std::move(fn),
843+
std::forward_as_tuple(std::forward<Args>(args)...)
844+
};
845+
}
846+
847+
Fn fn;
848+
};
849+
850+
template <typename Fn>
851+
auto lambda(Fn fn) {
852+
return lambda_callable<Fn>{std::move(fn)};
853+
}
854+
800855
} // namespace async

tests/algorithm.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,61 @@ TEST(Algorithm, Sequence) {
4747
for (int i = 0; i < 4; i++)
4848
ASSERT_EQ(steps[i], i);
4949
}
50+
51+
52+
struct dtor_counter {
53+
friend void swap(dtor_counter &a, dtor_counter &b) {
54+
std::swap(a.ctr_, b.ctr_);
55+
}
56+
57+
dtor_counter() :ctr_{nullptr} { }
58+
dtor_counter(int &ctr) :ctr_{&ctr} { }
59+
60+
~dtor_counter() {
61+
if (ctr_) (*ctr_)++;
62+
}
63+
64+
dtor_counter(const dtor_counter &) = delete;
65+
dtor_counter(dtor_counter &&other) :dtor_counter{} {
66+
swap(*this, other);
67+
}
68+
69+
dtor_counter &operator=(dtor_counter other) {
70+
swap(*this, other);
71+
return *this;
72+
}
73+
74+
int *ctr_;
75+
};
76+
77+
// These are defined as global so they are visible in lambdas without captures.
78+
79+
int g_lambda_ctr1;
80+
int g_lambda_ctr2;
81+
82+
bool g_lambda_ok1;
83+
bool g_lambda_ok2;
84+
85+
TEST(Algorithm, LambdaRaceAndCancel) {
86+
async::run(
87+
async::race_and_cancel(
88+
[x = dtor_counter{g_lambda_ctr1}] (async::cancellation_token ct)
89+
-> async::result<void> {
90+
if (g_lambda_ctr1 == 1) g_lambda_ok1 = true;
91+
co_return;
92+
},
93+
async::lambda(
94+
[x = dtor_counter{g_lambda_ctr2}] (async::cancellation_token ct)
95+
-> async::result<void> {
96+
if (g_lambda_ctr2 == 0) g_lambda_ok2 = true;
97+
co_return;
98+
}
99+
)
100+
));
101+
102+
ASSERT_EQ(g_lambda_ctr1, 1);
103+
ASSERT_TRUE(g_lambda_ok1);
104+
105+
ASSERT_EQ(g_lambda_ctr2, 1);
106+
ASSERT_TRUE(g_lambda_ok2);
107+
}

0 commit comments

Comments
 (0)