{"id":5886,"date":"2020-04-23T14:58:39","date_gmt":"2020-04-23T14:58:39","guid":{"rendered":"https:\/\/www.modernescpp.com\/index.php\/c-20-coroutine-abstraction-with-cppcoro\/"},"modified":"2023-09-28T07:42:49","modified_gmt":"2023-09-28T07:42:49","slug":"c-20-coroutine-abstraction-with-cppcoro","status":"publish","type":"post","link":"https:\/\/www.modernescpp.com\/index.php\/c-20-coroutine-abstraction-with-cppcoro\/","title":{"rendered":"C++20: Powerful Coroutines with cppcoro"},"content":{"rendered":"<p>I gave in my last post, &#8220;<a href=\"https:\/\/bit.ly\/CoroutinesCppcoro\">C++20: Coroutines with cppcoro<\/a>&#8220;, a basic introduction to the coroutines library from Lewis Baker. This introduction covered the elementary coroutines task and generator. Today, I add threads to tasks and get powerful abstractions.<\/p>\n<p><!--more--><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-8406 size-full\" style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/02\/TimelineCpp20Coroutines.png\" alt=\"\" width=\"1068\" height=\"383\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/02\/TimelineCpp20Coroutines.png 1068w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/02\/TimelineCpp20Coroutines-300x108.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/02\/TimelineCpp20Coroutines-1030x369.png 1030w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/02\/TimelineCpp20Coroutines-768x275.png 768w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/02\/TimelineCpp20Coroutines-705x253.png 705w\" sizes=\"auto, (max-width: 1068px) 100vw, 1068px\" \/><\/p>\n<p>Do you remember the previous post &#8220;<a href=\"https:\/\/bit.ly\/ThreadSynchronizationCoroutines\">C++20: Thread Synchronization with Coroutines<\/a>&#8220;? If no, I presented the challenges of a condition variable. A condition variable is a classical thread synchronization method, such as in a sender\/receiver or a producer\/consumer workflow. Condition variables have a big design flaw; they may be invoked without a notification (spurious wakeup) or overhear the notification (lost wakeup). In both cases, you may get a deadlock.\u00a0My following example on thread synchronization based on coroutines didn&#8217;t have the inherent risk of condition variables such as spurious or lost wakeup, but the example had another issue. It was too complicated.<\/p>\n<p>Thanks to <a href=\"https:\/\/github.com\/lewissbaker\/cppcoro\">cppcoro<\/a>, we can have the best of both worlds.: a straightforward event mechanism that does not have the design flaws of condition variables.<span style=\"color: #444444; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; background-color: #ffffff; float: none;\"><br \/>\n<\/span><\/p>\n<h2><span style=\"font-family: 'courier new', courier;\">single_consumer_event<\/span><\/h2>\n<p><span style=\"font-family: 'courier new', courier;\">single_consumer_event<\/span>\u00a0is, according to the documentation, a simple manual-reset event type that supports only a single coroutine awaiting it at a time. This is precisely, what I need:<\/p>\n<div style=\"background: #f0f3f3; overflow: auto; width: auto; gray;border-width: .1em .1em .1em .8em;\">\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #0099ff; font-style: italic;\">\/\/ cppcoroProducerConsumer.cpp<\/span>\n\n<span style=\"color: #009999;\">#include &lt;cppcoro\/single_consumer_event.hpp&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;cppcoro\/sync_wait.hpp&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;cppcoro\/task.hpp&gt;<\/span>\n\n<span style=\"color: #009999;\">#include &lt;future&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;iostream&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;string&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;thread&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;chrono&gt;<\/span>\n\ncppcoro<span style=\"color: #555555;\">::<\/span>single_consumer_event event;  \n\ncppcoro<span style=\"color: #555555;\">::<\/span>task<span style=\"color: #555555;\">&lt;&gt;<\/span> consumer() {\n    \n    <span style=\"color: #006699; font-weight: bold;\">auto<\/span> start <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span>chrono<span style=\"color: #555555;\">::<\/span>high_resolution_clock<span style=\"color: #555555;\">::<\/span>now();\n    \n    co_await event;  <span style=\"color: #0099ff; font-style: italic;\">\/\/ suspended until some thread calls event.set()<\/span>\n    \n    <span style=\"color: #006699; font-weight: bold;\">auto<\/span> end <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span>chrono<span style=\"color: #555555;\">::<\/span>high_resolution_clock<span style=\"color: #555555;\">::<\/span>now();\n    std<span style=\"color: #555555;\">::<\/span>chrono<span style=\"color: #555555;\">::<\/span>duration<span style=\"color: #555555;\">&lt;<\/span><span style=\"color: #007788; font-weight: bold;\">double<\/span><span style=\"color: #555555;\">&gt;<\/span> elapsed <span style=\"color: #555555;\">=<\/span> end <span style=\"color: #555555;\">-<\/span> start;\n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> <span style=\"color: #cc3300;\">\"Consumer waited \"<\/span> <span style=\"color: #555555;\">&lt;&lt;<\/span> elapsed.count() <span style=\"color: #555555;\">&lt;&lt;<\/span> <span style=\"color: #cc3300;\">\" seconds.\"<\/span> <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n  \n    co_return;\n}\n\n<span style=\"color: #007788; font-weight: bold;\">void<\/span> producer() {\n\n    <span style=\"color: #006699; font-weight: bold;\">using<\/span> <span style=\"color: #006699; font-weight: bold;\">namespace<\/span> std<span style=\"color: #555555;\">::<\/span>chrono_literals;\n    std<span style=\"color: #555555;\">::<\/span>this_thread<span style=\"color: #555555;\">::<\/span>sleep_for(<span style=\"color: #ff6600;\">2<\/span>s);\n    \n    event.set();  <span style=\"color: #0099ff; font-style: italic;\">\/\/ resumes the consumer  <\/span>\n    \n}\n\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> main() {\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    \n    <span style=\"color: #006699; font-weight: bold;\">auto<\/span> con <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span>async([]{ cppcoro<span style=\"color: #555555;\">::<\/span>sync_wait(consumer()); });  <span style=\"color: #0099ff; font-style: italic;\">\/\/ (1)<\/span>\n    <span style=\"color: #006699; font-weight: bold;\">auto<\/span> prod <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span>async(producer);                              <span style=\"color: #0099ff; font-style: italic;\">\/\/ (2)<\/span>\n    \n    con.get(), prod.get();\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    \n}\n<\/pre>\n<\/div>\n<p>The code should be self-explanatory. The consumer (line 1) and the producer (line 2) run in their thread. The call <span style=\"font-family: 'courier new', courier;\">cppcoro::sync_wait(consumer())<\/span>\u00a0(line 1) serves as a top-level task because the <span style=\"font-family: 'courier new', courier;\">main<\/span> function cannot be a coroutine. The call waits until the coroutine <span style=\"font-family: 'courier new', courier;\">consumer<\/span> is done. The coroutine <span style=\"font-family: 'courier new', courier;\">consumer<\/span> waits in the<span style=\"font-family: 'courier new', courier;\"> call co_await event<\/span> until someone calls<span style=\"font-family: 'courier new', courier;\"> event.set().<\/span> The function <span style=\"font-family: 'courier new', courier;\">producer<\/span> sends this event after a sleep of two seconds.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5883\" style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroProducerConsumer.png\" alt=\"cppcoroProducerConsumer\" width=\"400\" height=\"173\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroProducerConsumer.png 483w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroProducerConsumer-300x130.png 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/p>\n<p>Thanks to the cppcoro library, threads can be canceled.<\/p>\n<h2>Cancellation<\/h2>\n<p>The caller and the callee communicate with the<span style=\"font-family: 'courier new', courier;\"> cppcoro::cancellation_token<\/span>. The callee of the function that gets the request to cancel can respond in two ways.<\/p>\n<ol>\n<li>Poll at regular intervals for the request to cancel. The cppcoro::cancellation_token supports two member functions: <span style=\"font-family: 'courier new', courier;\">is_cancellation_requested()<\/span> and <span style=\"font-family: 'courier new', courier;\">throw_if_cancellation_requested()<\/span>.<\/li>\n<li>Register a callback that is executed in case of a cancellation request.<\/li>\n<\/ol>\n<p>The following example exemplifies the first use case.<\/p>\n<div style=\"background: #f0f3f3; overflow: auto; width: auto; gray;border-width: .1em .1em .1em .8em;\">\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #0099ff; font-style: italic;\">\/\/ cppcoroCancellation.cpp<\/span>\n\n<span style=\"color: #009999;\">#include &lt;chrono&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;iostream&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;future&gt;<\/span>\n\n<span style=\"color: #009999;\">#include &lt;cppcoro\/cancellation_token.hpp&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;cppcoro\/cancellation_source.hpp&gt;<\/span>\n\n<span style=\"color: #006699; font-weight: bold;\">using<\/span> <span style=\"color: #006699; font-weight: bold;\">namespace<\/span> std<span style=\"color: #555555;\">::<\/span>chrono_literals; \n\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> <span style=\"color: #cc00ff;\">main<\/span>() {\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    \n    cppcoro<span style=\"color: #555555;\">::<\/span>cancellation_source canSource;\n    cppcoro<span style=\"color: #555555;\">::<\/span>cancellation_token canToken <span style=\"color: #555555;\">=<\/span> canSource.token();  <span style=\"color: #0099ff; font-style: italic;\">\/\/ (1)<\/span>\n\n    <span style=\"color: #006699; font-weight: bold;\">auto<\/span> cancelSender <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span>async([<span style=\"color: #555555;\">&amp;<\/span>canSource] { \n        std<span style=\"color: #555555;\">::<\/span>this_thread<span style=\"color: #555555;\">::<\/span>sleep_for(<span style=\"color: #ff6600;\">2<\/span>s);\n        canSource.request_cancellation();                      <span style=\"color: #0099ff; font-style: italic;\">\/\/ (3)<\/span>\n        std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> <span style=\"color: #cc3300;\">\"canSource.request_cancellation() \"<\/span> <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    });\n        \n    <span style=\"color: #006699; font-weight: bold;\">auto<\/span> cancelReceiver <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span>async([<span style=\"color: #555555;\">&amp;<\/span>canToken] { \n        <span style=\"color: #006699; font-weight: bold;\">while<\/span>(<span style=\"color: #336666;\">true<\/span>) {\n            std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> <span style=\"color: #cc3300;\">\"Wait for cancellation request\"<\/span> <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n            std<span style=\"color: #555555;\">::<\/span>this_thread<span style=\"color: #555555;\">::<\/span>sleep_for(<span style=\"color: #ff6600;\">200<\/span>ms);\n            <span style=\"color: #006699; font-weight: bold;\">if<\/span> (canToken.is_cancellation_requested()) <span style=\"color: #006699; font-weight: bold;\">return<\/span>;  <span style=\"color: #0099ff; font-style: italic;\">\/\/ (2)<\/span>\n        }\n    });\n\n    cancelSender.get(), cancelReceiver.get();\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n\n}\n<\/pre>\n<\/div>\n<p>Line (1) shows the<span style=\"font-family: 'courier new', courier;\"> cancellation_token<\/span>, created by the <span style=\"font-family: 'courier new', courier;\">cancellation_source<\/span>. The caller <span style=\"font-family: 'courier new', courier;\">cancelSender<\/span> gets the cancellation source <span style=\"font-family: 'courier new', courier;\">canSource<\/span>, and the callee <span style=\"font-family: 'courier new', courier;\">cancelReceiver<\/span>\u00a0gets the cancellation token. The callee polls permanently for the cancellation request (line 2), which the caller sends via the call<span style=\"font-family: 'courier new', courier;\"> call.request_cancellation()<\/span> (line 3) after two seconds.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5884\" style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroCancellation.png\" alt=\"cppcoroCancellation\" width=\"400\" height=\"311\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroCancellation.png 503w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroCancellation-300x233.png 300w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/p>\n<p>I want to make two interesting observations.<\/p>\n<ol>\n<li>The cancellation is cooperative. If the callee ignores that cancellation request, nothing happens.<\/li>\n<li>We get with C++20 an improved <span style=\"font-family: 'courier new', courier;\">std::thread: std::jthread. std::jthread<\/span>\u00a0joins automatically in its destructor and can be in interrupted via an interrupt token. Read more details about the improved std::thread in my previous post: &#8220;<a href=\"https:\/\/www.modernescpp.com\/index.php\/a-new-thread-with-c-20-std-jthread\">A new Thread with C++20: std::jthread<\/a>&#8220;.<\/li>\n<\/ol>\n<p>cppcoro even supports a mutex.<\/p>\n<h2><span style=\"font-family: 'courier new', courier;\">async_mutex<\/span><\/h2>\n<p>A mutex such as <span style=\"font-family: 'courier new', courier;\">cppcoro::async_mutex<\/span> is a synchronization mechanism to protect shared data from being accessed by multiple threads simultaneously.<\/p>\n<div style=\"background: #f0f3f3; overflow: auto; width: auto; gray;border-width: .1em .1em .1em .8em;\">\n<pre style=\"margin: 0; line-height: 125%;\"><span style=\"color: #0099ff; font-style: italic;\">\/\/ cppcoroMutex.cpp<\/span>\n\n<span style=\"color: #009999;\">#include &lt;cppcoro\/async_mutex.hpp&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;cppcoro\/sync_wait.hpp&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;cppcoro\/task.hpp&gt;<\/span>\n\n<span style=\"color: #009999;\">#include &lt;iostream&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;thread&gt;<\/span>\n<span style=\"color: #009999;\">#include &lt;vector&gt;<\/span>\n\n\ncppcoro<span style=\"color: #555555;\">::<\/span>async_mutex mutex;\n\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> sum{};                                                                  <span style=\"color: #0099ff; font-style: italic;\">\/\/ (2)<\/span>\n\ncppcoro<span style=\"color: #555555;\">::<\/span>task<span style=\"color: #555555;\">&lt;&gt;<\/span> addToSum(<span style=\"color: #007788; font-weight: bold;\">int<\/span> num) {\n    cppcoro<span style=\"color: #555555;\">::<\/span>async_mutex_lock lockSum <span style=\"color: #555555;\">=<\/span> co_await mutex.scoped_lock_async(); <span style=\"color: #0099ff; font-style: italic;\">\/\/ (3)<\/span>  \n    sum <span style=\"color: #555555;\">+=<\/span> num;\n  \n}                                                                           <span style=\"color: #0099ff; font-style: italic;\">\/\/ (4)<\/span>\n\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> main() {\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    \n    std<span style=\"color: #555555;\">::<\/span>vector<span style=\"color: #555555;\">&lt;<\/span>std<span style=\"color: #555555;\">::<\/span><span style=\"color: #006699; font-weight: bold;\">thread<\/span><span style=\"color: #555555;\">&gt;<\/span> vec(<span style=\"color: #ff6600;\">10<\/span>);                                       <span style=\"color: #0099ff; font-style: italic;\">\/\/ (1)<\/span>\n    \n    <span style=\"color: #006699; font-weight: bold;\">for<\/span>(<span style=\"color: #006699; font-weight: bold;\">auto<\/span><span style=\"color: #555555;\">&amp;<\/span> thr<span style=\"color: #555555;\">:<\/span> vec) {\n        thr <span style=\"color: #555555;\">=<\/span> std<span style=\"color: #555555;\">::<\/span><span style=\"color: #006699; font-weight: bold;\">thread<\/span>([]{ <span style=\"color: #006699; font-weight: bold;\">for<\/span>(<span style=\"color: #007788; font-weight: bold;\">int<\/span> n <span style=\"color: #555555;\">=<\/span> <span style=\"color: #ff6600;\">0<\/span>; n <span style=\"color: #555555;\">&lt;<\/span> <span style=\"color: #ff6600;\">10<\/span>; <span style=\"color: #555555;\">++<\/span>n) cppcoro<span style=\"color: #555555;\">::<\/span>sync_wait(addToSum(n)); } );\n    }\n    \n    <span style=\"color: #006699; font-weight: bold;\">for<\/span>(<span style=\"color: #006699; font-weight: bold;\">auto<\/span><span style=\"color: #555555;\">&amp;<\/span> thr<span style=\"color: #555555;\">:<\/span> vec) thr.join();\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> <span style=\"color: #cc3300;\">\"sum: \"<\/span> <span style=\"color: #555555;\">&lt;&lt;<\/span> sum <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    \n    std<span style=\"color: #555555;\">::<\/span>cout <span style=\"color: #555555;\">&lt;&lt;<\/span> std<span style=\"color: #555555;\">::<\/span>endl;\n    \n}\n<\/pre>\n<\/div>\n<p>Line (1) creates ten threads. Each thread adds the numbers 0 to 9 to the shared <span style=\"font-family: 'courier new', courier;\">sum<\/span> (line 2). The function <span style=\"font-family: 'courier new', courier;\">addToSum<\/span> is the coroutine. The coroutine waits in the expression <span style=\"font-family: 'courier new', courier;\">co_await mutex.scoped_lock_async() (line 3) <\/span>until the mutex is acquired. The coroutine that waits for the mutex is not blocked but suspended. The previous lock-holder resumes the waiting coroutine in its <span style=\"font-family: 'courier new', courier;\">unlock<\/span> call.\u00a0 \u00a0As its name suggests, the mutex stays locked until the end of the scope (line 4).<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5885\" style=\"display: block; margin-left: auto; margin-right: auto;\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroMutex.png\" alt=\"cppcoroMutex\" width=\"300\" height=\"151\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroMutex.png 388w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2020\/04\/cppcoroMutex-300x151.png 300w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/p>\n<h2>What&#8217;s next?<\/h2>\n<p>Thanks to the function <span style=\"font-family: 'courier new', courier;\">cppcoro::when_all<\/span>, you can wait on one and more coroutines. I use<span style=\"font-family: 'courier new', courier;\">\u00a0cppcoro::when_all<\/span> with<span style=\"font-family: 'courier new', courier;\"> cppcoro::static_thread_pool<\/span>\u00a0in my <a href=\"https:\/\/www.modernescpp.com\/index.php\/c-20-thread-pools-with-cppcoro\">next post<\/a> to compose powerful workflows.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I gave in my last post, &#8220;C++20: Coroutines with cppcoro&#8220;, a basic introduction to the coroutines library from Lewis Baker. This introduction covered the elementary coroutines task and generator. Today, I add threads to tasks and get powerful abstractions.<\/p>\n","protected":false},"author":21,"featured_media":8406,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[375],"tags":[445],"class_list":["post-5886","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c-20","tag-coroutines"],"_links":{"self":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/5886","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/comments?post=5886"}],"version-history":[{"count":3,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/5886\/revisions"}],"predecessor-version":[{"id":8420,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/5886\/revisions\/8420"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media\/8406"}],"wp:attachment":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media?parent=5886"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/categories?post=5886"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/tags?post=5886"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}