Skip to content

Commit a1f9bd3

Browse files
authored
Files for coroutines lecture (#29)
1 parent ff3f8ce commit a1f9bd3

File tree

11 files changed

+1051
-0
lines changed

11 files changed

+1051
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,5 +68,6 @@ add_subdirectory(concurrency)
6868
add_subdirectory(queues)
6969
add_subdirectory(atomics)
7070
add_subdirectory(parallel)
71+
add_subdirectory(coroutines)
7172

7273
install()

coroutines/CMakeLists.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#------------------------------------------------------------------------------
2+
#
3+
# Source code for MIPT masters course on C++
4+
# Slides: https://sourceforge.net/projects/cpp-lects-rus
5+
# Licensed after GNU GPL v3
6+
#
7+
#------------------------------------------------------------------------------
8+
#
9+
# Build system for examples (coroutines lecture)
10+
#
11+
# Order of examples in SRCS variable corresponds to the order in the lecture.
12+
# Examples commented out are broken (often -- intentionally).
13+
#
14+
#------------------------------------------------------------------------------
15+
16+
set(SRCS
17+
natseq.cc
18+
resumable.cc
19+
natseq_range.cc
20+
natseq_zip.cc
21+
subscribers.cc
22+
# subscribers_mt.cc -- shall fail
23+
subscribers_natlst.cc
24+
cancellable.cc
25+
futures.cc
26+
)
27+
28+
add_gtest(coroutines)
29+
# add_demo(algo_bench)

coroutines/cancellable.cc

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//-----------------------------------------------------------------------------
2+
//
3+
// Source code for MIPT masters course on C++
4+
// Slides: https://sourceforge.net/projects/cpp-lects-rus
5+
// Licensed after GNU GPL v3
6+
//
7+
//-----------------------------------------------------------------------------
8+
//
9+
// Test for cancellables
10+
//
11+
//----------------------------------------------------------------------------
12+
13+
#include <coroutine>
14+
15+
#include "gtest/gtest.h"
16+
17+
namespace {
18+
19+
struct suspend_tunable {
20+
bool tune_;
21+
suspend_tunable(bool tune = true) : tune_(tune) {}
22+
bool await_ready() const noexcept { return tune_; }
23+
void await_suspend(std::coroutine_handle<>) const noexcept {}
24+
void await_resume() const noexcept {}
25+
};
26+
27+
struct resumable_cancelable {
28+
struct promise_type {
29+
bool is_cancelled = false;
30+
using coro_handle = std::coroutine_handle<promise_type>;
31+
auto get_return_object() { return coro_handle::from_promise(*this); }
32+
auto initial_suspend() { return std::suspend_always(); }
33+
auto final_suspend() noexcept { return std::suspend_always(); }
34+
void return_void() {}
35+
void unhandled_exception() { std::terminate(); }
36+
37+
auto await_transform(std::suspend_always) {
38+
if (is_cancelled)
39+
return suspend_tunable{true};
40+
return suspend_tunable{false};
41+
}
42+
};
43+
44+
using coro_handle = std::coroutine_handle<promise_type>;
45+
resumable_cancelable(coro_handle handle) : handle_(handle) { assert(handle); }
46+
resumable_cancelable(resumable_cancelable &) = delete;
47+
resumable_cancelable(resumable_cancelable &&rhs) : handle_(rhs.handle_) {
48+
rhs.handle_ = nullptr;
49+
}
50+
void cancel() {
51+
if (handle_.done())
52+
return;
53+
handle_.promise().is_cancelled = true;
54+
handle_.resume();
55+
}
56+
bool resume() {
57+
if (!handle_.done())
58+
handle_.resume();
59+
return !handle_.done();
60+
}
61+
~resumable_cancelable() {
62+
if (handle_)
63+
handle_.destroy();
64+
}
65+
66+
private:
67+
coro_handle handle_;
68+
};
69+
70+
} // namespace
71+
72+
namespace {
73+
74+
int G = 0;
75+
76+
resumable_cancelable foo() {
77+
for (int i = 0; i < 10; ++i) {
78+
G += 1;
79+
co_await std::suspend_always{};
80+
}
81+
}
82+
83+
} // namespace
84+
85+
TEST(coroutines, cancellable) {
86+
auto f = foo();
87+
EXPECT_EQ(G, 0);
88+
f.resume();
89+
EXPECT_EQ(G, 1);
90+
f.resume();
91+
EXPECT_EQ(G, 2);
92+
f.resume();
93+
EXPECT_EQ(G, 3);
94+
f.cancel();
95+
EXPECT_EQ(G, 10);
96+
}
97+
98+
// crutch for clang on godbolt
99+
#if defined(NOGTESTMAIN)
100+
int main(int argc, char **argv) {
101+
::testing::InitGoogleTest(&argc, argv);
102+
return RUN_ALL_TESTS();
103+
}
104+
#endif

coroutines/futures.cc

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
//-----------------------------------------------------------------------------
2+
//
3+
// Source code for MIPT masters course on C++
4+
// Slides: https://sourceforge.net/projects/cpp-lects-rus
5+
// Licensed after GNU GPL v3
6+
//
7+
//-----------------------------------------------------------------------------
8+
//
9+
// McNellis futures implementation for C++20
10+
//
11+
//----------------------------------------------------------------------------
12+
13+
#include <coroutine>
14+
#include <future>
15+
16+
#include "gtest/gtest.h"
17+
18+
template <> struct std::coroutine_traits<std::future<int>> {
19+
struct promise_type : std::promise<int> {
20+
std::future<int> get_return_object() { return this->get_future(); }
21+
std::suspend_never initial_suspend() noexcept { return {}; }
22+
std::suspend_never final_suspend() noexcept { return {}; }
23+
void return_value(int value) { this->set_value(value); }
24+
void unhandled_exception() {
25+
this->set_exception(std::current_exception());
26+
}
27+
};
28+
};
29+
30+
namespace {
31+
32+
auto operator co_await(std::future<int> future) {
33+
struct awaiter : std::future<int> {
34+
bool await_ready() { return false; } // suspend always
35+
void await_suspend(std::coroutine_handle<> handle) {
36+
std::thread([this, handle]() {
37+
this->wait();
38+
handle.resume();
39+
}).detach();
40+
}
41+
int await_resume() { return this->get(); }
42+
};
43+
return awaiter{std::move(future)};
44+
}
45+
46+
} // namespace
47+
48+
namespace {
49+
50+
std::future<int> compute_value() {
51+
int result = co_await std::async([] { return 42; });
52+
co_return result;
53+
}
54+
55+
} // namespace
56+
57+
TEST(coroutines, futures) {
58+
auto F = compute_value();
59+
auto V = F.get();
60+
EXPECT_EQ(V, 42);
61+
}
62+
63+
// crutch for clang on godbolt
64+
#if defined(NOGTESTMAIN)
65+
int main(int argc, char **argv) {
66+
::testing::InitGoogleTest(&argc, argv);
67+
return RUN_ALL_TESTS();
68+
}
69+
#endif

coroutines/natseq.cc

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//-----------------------------------------------------------------------------
2+
//
3+
// Source code for MIPT masters course on C++
4+
// Slides: https://sourceforge.net/projects/cpp-lects-rus
5+
// Licensed after GNU GPL v3
6+
//
7+
//-----------------------------------------------------------------------------
8+
//
9+
// Natural numbers sequence
10+
//
11+
//----------------------------------------------------------------------------
12+
13+
#include <coroutine>
14+
15+
#include "gtest/gtest.h"
16+
17+
namespace {
18+
19+
template <typename T> struct generator {
20+
struct promise_type {
21+
T current_value;
22+
using coro_handle = std::coroutine_handle<promise_type>;
23+
auto get_return_object() { return coro_handle::from_promise(*this); }
24+
auto initial_suspend() { return std::suspend_always(); }
25+
auto final_suspend() noexcept { return std::suspend_always(); }
26+
void return_void() {}
27+
void unhandled_exception() { std::terminate(); }
28+
auto yield_value(T value) {
29+
current_value = value;
30+
return std::suspend_always{};
31+
}
32+
};
33+
34+
using coro_handle = std::coroutine_handle<promise_type>;
35+
bool move_next() {
36+
return handle_ ? (handle_.resume(), !handle_.done()) : false;
37+
}
38+
T current_value() const { return handle_.promise().current_value; }
39+
generator(coro_handle h) : handle_(h) {}
40+
generator(generator const &) = delete;
41+
generator(generator &&rhs) : handle_(rhs.handle_) { rhs.handle_ = nullptr; }
42+
~generator() {
43+
if (handle_)
44+
handle_.destroy();
45+
}
46+
47+
private:
48+
coro_handle handle_;
49+
};
50+
51+
} // namespace
52+
53+
namespace {
54+
55+
generator<int> natural_nums() {
56+
int num = 0;
57+
for (;;) {
58+
co_yield num;
59+
num += 1;
60+
}
61+
}
62+
63+
} // namespace
64+
65+
TEST(coroutines, natseq) {
66+
int n = 0;
67+
auto nums = natural_nums();
68+
69+
for (int i = 0; i < 10; ++i) {
70+
nums.move_next();
71+
auto y = nums.current_value();
72+
EXPECT_EQ(y, n);
73+
n = n + 1;
74+
}
75+
}
76+
77+
// crutch for clang on godbolt
78+
#if defined(NOGTESTMAIN)
79+
int main(int argc, char **argv) {
80+
::testing::InitGoogleTest(&argc, argv);
81+
return RUN_ALL_TESTS();
82+
}
83+
#endif

0 commit comments

Comments
 (0)