1290 | | // Note that constructing the promise wrap type does not necessarily construct the wrapped promise i.e. |
1291 | | // actual construction may occur lazily |
1292 | | auto __resume_promise(std::resumable_traits<std::future<int>>::promise_type()); |
1293 | | if(__resume_promise.initial_suspend()) |
1294 | | return suspend_now(); // Not expanded for brevity |
1295 | | |
1296 | | int total=0; |
1297 | | for(size_t n=0; n<10; n++) |
1298 | | { |
1299 | | std::future<int> &&__temp=async_call(); // Bind to rvalue ref, no moving |
1300 | | if(!__temp.await_ready()) // defaults to __temp.is_ready() |
| 1292 | |
| 1293 | // Always dynamically allocate a context on entry to a resumable function (the frame allocator defaults to std::allocator<char>) |
| 1294 | auto &__frame_allocator=std::coroutine_traits<std::future<int>>::get_allocator(); |
| 1295 | struct __context_type |
| 1296 | { |
| 1297 | std::coroutine_traits<std::future<int>>::promise_type __resume_promise; |
| 1298 | ... <args, stack, state> ... |
| 1299 | } *__context=__frame_allocator.allocate(sizeof(__context_type)); |
| 1300 | |
| 1301 | // Construct the context frame |
| 1302 | new (__context) __context_type(<args, stack, state>); |
| 1303 | |
| 1304 | // Generate a future for the completion of this function |
| 1305 | auto __return_object(__context->__resume_promise.get_return_object()); |
| 1306 | |
| 1307 | if(__context->__resume_promise.initial_suspend()) |
| 1308 | return suspend_now(); // Not expanded for brevity |
| 1309 | |
| 1310 | try |
| 1311 | { |
| 1312 | int total=0; |
| 1313 | for(size_t n=0; n<10; n++) |
1302 | | // The frame allocator defaults to std::allocator<char> |
1303 | | auto &&__frame_allocator=std::resumable_traits<std::future<int>>::get_allocator(); |
1304 | | |
1305 | | // Internally generated by the compiler to resume me at the resume_me_detached goto label after storing my state into |
1306 | | // memory allocated using __frame_allocator |
1307 | | unspecified_callable &&__resume_me(__frame_allocator, __resume_promise, resume_me_detached); |
1308 | | |
1309 | | // defaults to __temp.then(__resume_me). __resume_me will be executed immediately after __temp.set_value() by the thread calling set_value(). |
1310 | | __temp.await_suspend(__resume_me); |
1311 | | |
1312 | | // Return a future for the completion of this function. If the internal promise object is |
1313 | | // lazily constructed, now is when it actually occurs |
1314 | | return __resume_promise.get_return_object(); |
| 1315 | std::future<int> &&__temp=async_call(); // Bind to rvalue ref, no moving |
| 1316 | if(!__temp.await_ready()) // defaults to __temp.is_ready() |
| 1317 | { |
| 1318 | // Internally generated by the compiler to resume me at the resume_me_detached goto label |
| 1319 | // after storing my state into __context |
| 1320 | unspecified_callable &&__resume_me(__frame_allocator, __resume_promise, resume_me_detached); |
| 1321 | |
| 1322 | // Have __temp, when signalled, resume my execution. defaults to __temp.then(__resume_me). |
| 1323 | // __resume_me will be executed immediately after __temp.set_value() by the thread calling set_value(). |
| 1324 | __temp.await_suspend(__resume_me); |
| 1325 | |
| 1326 | return __return_object; // exits function with future, will resume and signal later |
| 1327 | } |
| 1328 | never_resumed: // This path only taken if function never resumed |
| 1329 | total+=__temp.await_resume(); // defaults to __temp.get(), this is always ready and does not block |
1331 | | // defaults to __temp.then(__resume_me). __resume_me will be executed immediately after __temp.set_value() by the thread calling set_value(). |
1332 | | __temp.await_suspend(__resume_me); |
1333 | | |
1334 | | // Suspend myself using previously allocated __resume_me |
1335 | | __resume_me.suspend(); |
| 1348 | std::future<int> &&__temp=async_call(); // Bind to rvalue ref, no moving |
| 1349 | if(!__temp.await_ready()) // defaults to __temp.is_ready() |
| 1350 | { |
| 1351 | // Have __temp, when signalled, resume my execution. defaults to __temp.then(__resume_me). |
| 1352 | // __resume_me will be executed immediately after __temp.set_value() by the thread calling set_value(). |
| 1353 | __temp.await_suspend(__resume_me); |
| 1354 | |
| 1355 | // Suspend myself using previously allocated __resume_me |
| 1356 | __resume_me.suspend(); |
| 1357 | } |
| 1358 | resume_me_detached: // This path taken if function ever resumed |
| 1359 | total+=__temp.await_resume(); // defaults to __temp.get(), this is always ready and does not block |
1337 | | resume_me_detached: // This path taken if function ever resumed |
1338 | | total+=__temp.await_resume(); // defaults to __temp.get() |
1339 | | } |
1340 | | if(__resume_promise.final_suspend()) |
1341 | | return suspend_now(); // Not expanded for brevity |
1342 | | __resume_promise.set_result(total); // Signals the earlier returned future |
| 1361 | __context->__resume_promise.set_value(total); |
| 1362 | } |
| 1363 | catch(...) |
| 1364 | { |
| 1365 | __context->__resume_promise.set_exception(std::current_exception()); |
| 1366 | } |
| 1367 | if(__context->__resume_promise.final_suspend()) |
| 1368 | return suspend_now(); // Not expanded for brevity, jumps to resume_me_addr path |
| 1369 | __context->~__context_type(); |
| 1370 | __frame_allocator.deallocate(__context, sizeof(__context_type)); |
1353 | | This boilerplate expanded version may still hurt the head, so here is essentially what happens: if `async_call()` ever returns an unsignaled future, `accumulate()` will construct a promise-future pair, allocate memory to store its stack frame, schedule the resumption of its detached self as a continuation of the future returned from `async_call()` and return the newly constructured future to `main()`. When the future returned by `async_call()` is signalled, this schedules the resumption of the detached version of `accumulate()` on the thread it was originally running on. This resumed `accumulate()` may suspend and resume itself many more times on the future returned by repeated calls of `async_call()`, but it eventually will signal the future it returned earlier with the result, unblocking `main()`. |
1354 | | |
1355 | | The `await_ready(), await_suspend()` and `await_resume()` functions are firstly looked up as member functions of the synchronisation type. If not present, they are then looked up as free functions within the same namespace as the synchronisation type with usual overload resolution. |
| 1381 | This boilerplate expanded version may still hurt the head, so here is essentially what happens: |
| 1382 | 1. We dynamically allocate a stack frame on entry, and that dynamic memory allocation sticks around until the resumable function completes. |
| 1383 | 2. We always construct a promise-future pair for the result (or whatever synchronisation object `coroutine_traits` says) on entry to the function. |
| 1384 | 3. If `async_call()` always returns a signalled future, we add the result to total, signal the future and return the future. |
| 1385 | 4. If `async_call()` ever returns an unsignaled future, we ask the unsignaled future to resume our detached path when it is signalled. We suspend ourselves, and return our future immediately. At some point our detached path will signal the future. |
| 1386 | |
| 1387 | The `await_ready(), await_suspend()` and `await_resume()` functions are firstly looked up as member functions of the synchronisation type returned by the traits specialisation. If not present, they are then looked up as free functions within the same namespace as the synchronisation type with usual overload resolution. |
1380 | | This just works because `generator<int>` provides an iterator at `generator<int>::iterator` and a promise type at `generator<int>::promise_type`. The iterator, when dereferenced, causes a single iteration of `fib()` between the `yield` statements and the value yielded is output by the iterator. |
| 1412 | This just works because `generator<int>` provides an iterator at `generator<int>::iterator` and a promise type at `generator<int>::promise_type`. The iterator, when dereferenced, causes a single iteration of `fib()` between the `yield` statements and the value yielded is output by the iterator. Note that for each iteration, a new promise and future pair is created, then destroyed and created, and so on until the generator returns. |
| 1413 | |
| 1414 | All this is great if you are on Microsoft's compiler, but what about the rest of us before C++ 1z? Luckily [http://olk.github.io/libs/fiber/doc/html/ Boost has a conditionally accepted library called Boost.Fiber] which was one of the C++ 11/14 libraries reviewed above and this, with a few caveats, provides good feature parity with proposed C++1z coroutines. Boost.Fiber provides a mirror image of the STL threading library, so: |
| 1415 | |
| 1416 | || `std::thread` || => || `fiber::fiber` || |
| 1417 | || `std::this_thread` || => || `fiber::this_fiber` || |
| 1418 | || `std::mutex` || => || `fiber::mutex` || |
| 1419 | || `std::condition_variable` || => || `fiber::condition_variable` || |
| 1420 | || `std::future<T>` || => || `fiber::future<T>` || |
| 1421 | |
| 1422 | Rewriting the above example to use Boost.Fiber instead (TODO): |
| 1423 | |
| 1424 | {{{#!c++ |
| 1425 | // This is some function which schedules some async operation whose result is returned via the future<int> |
| 1426 | extern std::future<int> async_call(); |
| 1427 | |
| 1428 | // This is a function which calls async_call ten times, suspending itself while async_call runs. |
| 1429 | std::future<int> accumulate() |
| 1430 | { |
| 1431 | int total=0; |
| 1432 | for(size_t n=0; n<10; n++) |
| 1433 | total+=await async_call(); // Note the await keyword, this is where the function might suspend |
| 1434 | return total; |
| 1435 | } |
| 1436 | |
| 1437 | int main(void) |
| 1438 | { |
| 1439 | std::cout << "Total is " << accumulate().get() << std::endl; |
| 1440 | return 0; |
| 1441 | } |
| 1442 | }}} |