{"id":6180,"date":"2021-07-16T07:51:30","date_gmt":"2021-07-16T07:51:30","guid":{"rendered":"https:\/\/www.modernescpp.com\/index.php\/parallel-algorithms-of-the-stl-with-gcc\/"},"modified":"2023-06-26T09:28:58","modified_gmt":"2023-06-26T09:28:58","slug":"parallel-algorithms-of-the-stl-with-gcc","status":"publish","type":"post","link":"https:\/\/www.modernescpp.com\/index.php\/parallel-algorithms-of-the-stl-with-gcc\/","title":{"rendered":"Parallel Algorithms of the STL with the GCC Compiler"},"content":{"rendered":"<p>GCC supports my favorite C++17 feature: the Standard Template Library (STL) parallel algorithms. I recognized this a few days ago, and I&#8217;m happy to write a post about it and share my enthusiasm.<\/p>\n<p><!--more--><\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-6176\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/timelineParallelSTL.png\" alt=\"timelineParallelSTL\" width=\"650\" height=\"249\" style=\"display: block; margin-left: auto; margin-right: auto;\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/timelineParallelSTL.png 927w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/timelineParallelSTL-300x115.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/timelineParallelSTL-768x294.png 768w\" sizes=\"auto, (max-width: 650px) 100vw, 650px\" \/><\/p>\n<p>The Microsoft compiler has supported parallel algorithms since its beginning but sadly, neither GCC nor Clang. I have to be precise; GCC 9 allows you to use parallel algorithms. Before I show you examples with performance numbers in my next post, I want to write about the parallel algorithms of the STL and give you the necessary information.<\/p>\n<h2>Parallel Algorithms of the Standard Template Library<\/h2>\n<p>The Standard Template Library has more than 100 algorithms for searching, counting, and manipulating ranges and their elements. With C++17, 69 get new overloads, and new ones are added. The overloaded and new algorithms can be invoked with a so-called execution policy. Using an execution policy, you can specify whether the algorithm should run sequentially, in parallel, or parallel with vectorization. For using the execution policy, you have to include the header <code>&lt;execution&gt;<\/code>.<\/p>\n<h3>Execution Policy<\/h3>\n<div>\n<div>The C++17 standard&nbsp;defines&nbsp;three&nbsp;execution&nbsp;policies:<\/div>\n<p><\/p>\n<ul>\n<li><code>std::execution::sequenced_policy<\/code><\/li>\n<li><code>std::execution::parallel_policy<\/code><\/li>\n<li><code>std::execution::parallel_unsequenced_policy<\/code><\/li>\n<\/ul>\n<p><\/p>\n<div>The&nbsp;corresponding&nbsp;policy&nbsp;tag&nbsp;specifies&nbsp;whether&nbsp;a&nbsp;program&nbsp;should&nbsp;run&nbsp;sequentially,&nbsp;in&nbsp;parallel,&nbsp;or in parallel&nbsp;with&nbsp;vectorization.<\/div>\n<p><\/p>\n<ul>\n<li><code>std::execution::seq<\/code>:&nbsp;runs&nbsp;the&nbsp;program&nbsp;sequentially<\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><code>std::execution::par<\/code>:&nbsp;runs&nbsp;the&nbsp;program&nbsp;in&nbsp;parallel&nbsp;on&nbsp;multiple&nbsp;threads<\/li>\n<\/ul>\n<p><\/p>\n<ul>\n<li><code>std::execution::par_unseq<\/code>:&nbsp;runs&nbsp;the&nbsp;program&nbsp;in&nbsp;parallel&nbsp;on&nbsp;multiple&nbsp;threads&nbsp;and&nbsp;allows&nbsp;the&nbsp;interleaving&nbsp;of&nbsp;individual&nbsp;loops;&nbsp;permits&nbsp;a vectorized version&nbsp;with <a href=\"https:\/\/en.wikipedia.org\/wiki\/SIMD\">SIMD<\/a> (<strong>S<\/strong>ingle<strong> I<\/strong>nstruction <strong>M<\/strong>ultiple<strong> D<\/strong>ata).<\/li>\n<\/ul>\n<p><\/p>\n<div>The&nbsp;usage&nbsp;of&nbsp;the&nbsp;execution&nbsp;policy<code> std::execution::par<\/code> or <code>std::execution::par_unseq<\/code> allows&nbsp;the&nbsp;algorithm&nbsp;to&nbsp;run&nbsp;parallel&nbsp;or&nbsp;parallel&nbsp;and&nbsp;vectorized.&nbsp;This&nbsp;policy&nbsp;is&nbsp;a&nbsp;permission&nbsp;and&nbsp;not&nbsp;a&nbsp;requirement.<code><\/code><code><\/code><\/div>\n<p><\/p>\n<div>The&nbsp;following&nbsp;code&nbsp;snippet&nbsp;applies to all&nbsp;execution&nbsp;policies.<\/div>\n<p> <!-- HTML generated using hilite.me --><\/p>\n<div style=\"background: #f0f3f3; overflow: auto; width: auto; gray;border-width: .1em .1em .1em .8em;\">\n<pre style=\"margin: 0px; line-height: 125%;\">std<span style=\"color: #555555;\">::<\/span>vector<span style=\"color: #555555;\">&lt;<\/span><span style=\"color: #007788; font-weight: bold;\">int<\/span><span style=\"color: #555555;\">&gt;<\/span> v <span style=\"color: #555555;\">=<\/span> {<span style=\"color: #ff6600;\">1<\/span>, <span style=\"color: #ff6600;\">2<\/span>, <span style=\"color: #ff6600;\">3<\/span>, <span style=\"color: #ff6600;\">4<\/span>, <span style=\"color: #ff6600;\">5<\/span>, <span style=\"color: #ff6600;\">6<\/span>, <span style=\"color: #ff6600;\">7<\/span>, <span style=\"color: #ff6600;\">8<\/span>, <span style=\"color: #ff6600;\">9<\/span>};\r\n\r\n<span style=\"color: #0099ff; font-style: italic;\">\/\/ standard sequential sort                              <\/span>\r\nstd<span style=\"color: #555555;\">::<\/span>sort(v.begin(), v.end());                            <span style=\"color: #0099ff;\"> \/\/ (1)<\/span>\r\n\r\n<span style=\"color: #0099ff; font-style: italic;\">\/\/ sequential execution<\/span>\r\nstd<span style=\"color: #555555;\">::<\/span>sort(std<span style=\"color: #555555;\">::<\/span>execution<span style=\"color: #555555;\">::<\/span>seq, v.begin(), v.end());        <span style=\"color: #0099ff;\">\/\/ (2)<\/span>\r\n\r\n<span style=\"color: #0099ff; font-style: italic;\">\/\/ permitting parallel execution<\/span>\r\nstd<span style=\"color: #555555;\">::<\/span>sort(std<span style=\"color: #555555;\">::<\/span>execution<span style=\"color: #555555;\">::<\/span>par, v.begin(), v.end());        <span style=\"color: #0099ff;\">\/\/ (3)<\/span>\r\n\r\n<span style=\"color: #0099ff; font-style: italic;\">\/\/ permitting parallel and vectorized execution<\/span>\r\nstd<span style=\"color: #555555;\">::<\/span>sort(std<span style=\"color: #555555;\">::<\/span>execution<span style=\"color: #555555;\">::<\/span>par_unseq, v.begin(), v.end());  <span style=\"color: #0099ff;\">\/\/ (4)\r\n<\/span><\/pre>\n<\/div>\n<p><\/p>\n<div>The&nbsp;example&nbsp;shows&nbsp;that&nbsp;you&nbsp;can&nbsp;still&nbsp;use&nbsp;the&nbsp;classic&nbsp;variant&nbsp;of <code>std::sort<\/code> (4).&nbsp;Besides,&nbsp;in&nbsp;C++17,&nbsp;you&nbsp;can&nbsp;specify&nbsp;explicitly&nbsp;whether&nbsp;the&nbsp;sequential&nbsp;(2),&nbsp;parallel&nbsp;(3),&nbsp;or parallel&nbsp;and&nbsp;vectorized&nbsp;(4)&nbsp;version&nbsp;should&nbsp;be&nbsp;used.<\/div>\n<div>&nbsp;<\/div>\n<div><\/div>\n<\/div>\n<h3>Parallel and Vectorized Execution<\/h3>\n<div>Whether an algorithm runs in a parallel and vectorized way depends on many factors. For example, it depends on whether the CPU and the operating system support SIMD instructions. It also depends on the compiler and the optimization level you used to translate your code.<\/div>\n<div>The following example shows a simple loop for filling a vector.<\/div>\n<div>&nbsp;<\/div>\n<p><!-- HTML generated using hilite.me --><\/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: #006699; font-weight: bold;\">const<\/span> <span style=\"color: #007788; font-weight: bold;\">int<\/span> SIZE <span style=\"color: #555555;\">=<\/span> <span style=\"color: #ff6600;\">8<\/span>;\r\n \r\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> vec[] <span style=\"color: #555555;\">=<\/span> {<span style=\"color: #ff6600;\">1<\/span>, <span style=\"color: #ff6600;\">2<\/span>, <span style=\"color: #ff6600;\">3<\/span>, <span style=\"color: #ff6600;\">4<\/span>, <span style=\"color: #ff6600;\">5<\/span>, <span style=\"color: #ff6600;\">6<\/span>, <span style=\"color: #ff6600;\">7<\/span>, <span style=\"color: #ff6600;\">8<\/span>};\r\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> res[] <span style=\"color: #555555;\">=<\/span> {<span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>, <span style=\"color: #ff6600;\">0<\/span>};\r\n \r\n<span style=\"color: #007788; font-weight: bold;\">int<\/span> <span style=\"color: #cc00ff;\">main<\/span>() {\r\n    <span style=\"color: #006699; font-weight: bold;\">for<\/span> (<span style=\"color: #007788; font-weight: bold;\">int<\/span> i <span style=\"color: #555555;\">=<\/span> <span style=\"color: #ff6600;\">0<\/span>; i <span style=\"color: #555555;\">&lt;<\/span> SIZE; <span style=\"color: #555555;\">++<\/span>i) {\r\n        res[i] <span style=\"color: #555555;\">=<\/span> vec[i]<span style=\"color: #555555;\">+<\/span><span style=\"color: #ff6600;\">5<\/span>;\r\n    }\r\n}\r\n<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>The expression<code> res[i] = vec[i] + 5<\/code> is the crucial line in this small example. Thanks to <a href=\"https:\/\/godbolt.org\/\">Compiler Explorer<\/a>, we can look closer at the assembler instructions generated by clang 3.6.<\/p>\n<h4>Without Optimization<\/h4>\n<p>Here are the assembler instructions. Each addition is done sequentially.<\/p>\n<p>&nbsp;<img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5192\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2017\/02\/seq.png\" alt=\"seq\" width=\"282\" height=\"93\" style=\"display: block; margin-left: auto; margin-right: auto;\" \/><\/p>\n<h4>With Maximum Optimization<\/h4>\n<p>By using the highest optimization level, -O3, special registers such as <code>xmm0<\/code> are used that can hold 128 bits or 4 ints. This special register means that the addition takes place in parallel on four vector elements.<\/p>\n<p>&nbsp;<img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-5193\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2017\/02\/vec.png\" alt=\"vec\" width=\"468\" height=\"138\" style=\"display: block; margin-left: auto; margin-right: auto;\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2017\/02\/vec.png 468w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2017\/02\/vec-300x88.png 300w\" sizes=\"auto, (max-width: 468px) 100vw, 468px\" \/><\/p>\n<p>An overload of an algorithm without an execution policy and an overload of an algorithm with a sequential execution policy <code>std::execution::seq<\/code> differ in one aspect: exceptions.<\/p>\n<p>&nbsp;<\/p>\n<h3>Exceptions<\/h3>\n<p>If an exception occurs during the usage of an algorithm with an execution policy,<a href=\"https:\/\/en.cppreference.com\/w\/cpp\/error\/terminate\"><code>std::terminate<\/code><\/a> it is called.<code> std::terminate<\/code> calls the installed<a href=\"https:\/\/en.cppreference.com\/w\/cpp\/error\/terminate_handler\"><code>std::terminate_handler<\/code><\/a>. The consequence is that per default <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/utility\/program\/abort\"><code>std::abort<\/code><\/a> is called, which causes abnormal program termination. The handling of exceptions is the difference between an algorithm\u2019s invocation without an execution policy and an algorithm with a sequential <code>std::execution::seq<\/code> execution policy. The invocation of the algorithm without an execution policy propagates the exception, and, therefore, the exception can be handled.<\/p>\n<p>With C++17, 69 of the STL algorithms got new overloads, and new algorithms were added.<\/p>\n<h3>Algorithms<\/h3>\n<p>Here are the 69 algorithms with parallelized versions.<\/p>\n<h4>&nbsp;<img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-6177\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/allAlgorithm.png\" alt=\"allAlgorithm\" width=\"700\" height=\"326\" style=\"display: block; margin-left: auto; margin-right: auto;\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/allAlgorithm.png 1223w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/allAlgorithm-300x140.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/allAlgorithm-1024x477.png 1024w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/allAlgorithm-768x358.png 768w\" sizes=\"auto, (max-width: 700px) 100vw, 700px\" \/><\/h4>\n<h4>The New Algorithms<\/h4>\n<p>The new algorithm in C++17, which are designed for parallel execution, are in the <code>std<\/code> namespace and need the header <code>&lt;numeric&gt;<\/code>.<\/p>\n<ul>\n<li><code>std::exclusive_scan: <\/code>Applies from the left a binary callable up to the range&#8217;s ith (exclusive) element. The left argument of the callable is the previous result\u2014stores intermediate results.<\/li>\n<li><code>std::inclusive_scan<\/code>: Applies from the left a binary callable up to the range&#8217;s ith (inclusive) element. The left argument of the callable is the previous result\u2014stores intermediate results.<\/li>\n<li><code>std::transform_exclusive_scan<\/code>: First applies a unary callable to the range and then applies<code> std::exclusive_scan<\/code>.<\/li>\n<li><code>std::transform_inclusive_scan<\/code>: First applies a unary callable to the range and then applies <code>std::inclusive_scan<\/code>.<\/li>\n<li><code>std::reduce<\/code>: Applies a binary callable to the range.<\/li>\n<li><code>std::transform_reduce<\/code>: Applies first a unary callable to one or a binary callable to two ranges and then<code> std::reduce<\/code> to the resulting range.<\/li>\n<\/ul>\n<p>Admittedly this description is not easy to digest, but if you already know <code>std::accumulat<\/code>e and<code> std::partial_sum<\/code>, the reduce and scan variations should be pretty familiar. <code>std::reduce<\/code> is the parallel pendant to std::accumulate and scan the parallel pendant to partial_sum. The parallel execution is the reason that<code> std::reduce<\/code> needs an associative and commutative callable. The corresponding statement holds for the scan variations contrary to the partial_sum variations. To get the full details, visit<a href=\"https:\/\/en.cppreference.com\/w\/cpp\/algorithm\"> cppreferenc.com\/algorithm<\/a>.<\/p>\n<p>You may wonder why we need <code>std::reduce<\/code> for parallel execution because we already have<code> std::accumulate<\/code>. The reason is that<code> std::accumulate<\/code> it processes its elements in an order that cannot be parallelized.<\/p>\n<h4><code>std::accumulate<\/code> versus<code> std::reduce<\/code><\/h4>\n<p>While<code> std::accumulate<\/code> processes its elements from left to right,<code> std::reduce<\/code> and does it in an arbitrary order. Let me start with a small code snippet using<code> std::accumulate<\/code> and <code>std::reduce<\/code>. The callable is the lambda function<code> [](int a, int b){ return a * b; }<\/code>.<\/p>\n<p>&nbsp;<\/p>\n<p><!-- HTML generated using hilite.me --><\/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%;\">std<span style=\"color: #555555;\">::<\/span>vector<span style=\"color: #555555;\">&lt;<\/span><span style=\"color: #007788; font-weight: bold;\">int<\/span><span style=\"color: #555555;\">&gt;<\/span> v{<span style=\"color: #ff6600;\">1<\/span>, <span style=\"color: #ff6600;\">2<\/span>, <span style=\"color: #ff6600;\">3<\/span>, <span style=\"color: #ff6600;\">4<\/span>};\r\n\r\nstd<span style=\"color: #555555;\">::<\/span>accumulate(v.begin(), v.end(), <span style=\"color: #ff6600;\">1<\/span>, [](<span style=\"color: #007788; font-weight: bold;\">int<\/span> a, <span style=\"color: #007788; font-weight: bold;\">int<\/span> b){ <span style=\"color: #006699; font-weight: bold;\">return<\/span> a <span style=\"color: #555555;\">*<\/span> b; });\r\nstd<span style=\"color: #555555;\">::<\/span>reduce(std<span style=\"color: #555555;\">::<\/span>execution<span style=\"color: #555555;\">::<\/span>par, v.begin(), v.end(), <span style=\"color: #ff6600;\">1<\/span> , [](<span style=\"color: #007788; font-weight: bold;\">int<\/span> a, <span style=\"color: #007788; font-weight: bold;\">int<\/span> b){ <span style=\"color: #006699; font-weight: bold;\">return<\/span> a <span style=\"color: #555555;\">*<\/span> b; });\r\n<\/pre>\n<\/div>\n<p>&nbsp;<\/p>\n<p>The two following graphs show the different processing strategies of<code> std::accumulate<\/code> and <code>std::reduce<\/code>.<\/p>\n<ul>\n<li><code>std::accumulate<\/code> starts at the left and successively applies the binary operator.<\/li>\n<\/ul>\n<p>&nbsp;<img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-6178\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/AccumulateNew.png\" alt=\"AccumulateNew\" width=\"400\" height=\"255\" style=\"display: block; margin-left: auto; margin-right: auto;\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/AccumulateNew.png 858w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/AccumulateNew-300x191.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/AccumulateNew-768x490.png 768w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/p>\n<ul>\n<li>On the contrary, <code>std::reduce<\/code> applies the binary operator in a non-deterministic way.<\/li>\n<\/ul>\n<p>&nbsp;<img loading=\"lazy\" decoding=\"async\" class=\" size-full wp-image-6179\" src=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/ReduceNew.png\" alt=\"ReduceNew\" width=\"400\" height=\"222\" style=\"display: block; margin-left: auto; margin-right: auto;\" srcset=\"https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/ReduceNew.png 883w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/ReduceNew-300x166.png 300w, https:\/\/www.modernescpp.com\/wp-content\/uploads\/2021\/07\/ReduceNew-768x425.png 768w\" sizes=\"auto, (max-width: 400px) 100vw, 400px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>The associativity of the callable allows the<code> std::reduce<\/code> algorithm to apply the reduction step on arbitrary adjacents pairs of elements. The intermediate results can be computed in an arbitrary order thanks to commutativity.<\/p>\n<h2>What&#8217;s next?<\/h2>\n<p>As promised, my <a href=\"https:\/\/www.modernescpp.com\/index.php\/performance-of-the-parallel-stl-algorithmn\">next post <\/a>uses parallel algorithms of the STL and provides performance numbers for the Microsoft compiler and the GCC.<\/p>\n<h2><span class=\"css-901oao css-16my406 r-poiln3 r-bcqeeo r-qvutc0\">Five Vouchers for Stephan Roth&#8217;s Book &#8220;Clean C++20&#8221; to Win <\/span><\/h2>\n<p><span class=\"css-901oao css-16my406 r-poiln3 r-bcqeeo r-qvutc0\">I am giving away five vouchers for Stephan Roth&#8217;s book &#8220;Clean C++20&#8221;, sponsored by the book&#8217;s publisher Apress. Here is how you can get it: <\/span><a href=\"https:\/\/t.co\/CzCtb9errG?amp=1\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"css-4rbku5 css-18t94o4 css-901oao css-16my406 r-1n1174f r-1loqt21 r-poiln3 r-bcqeeo r-qvutc0\" dir=\"ltr\" role=\"link\"><span class=\"css-901oao css-16my406 r-poiln3 r-hiw28u r-qvk6io r-bcqeeo r-qvutc0\" aria-hidden=\"true\">https:\/\/<\/span>bit.ly\/StephanRoth<\/a><span class=\"css-901oao css-16my406 r-poiln3 r-bcqeeo r-qvutc0\">.<\/span><\/p>\n<p>&nbsp;<\/p>\n<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>GCC supports my favorite C++17 feature: the Standard Template Library (STL) parallel algorithms. I recognized this a few days ago, and I&#8217;m happy to write a post about it and share my enthusiasm.<\/p>\n","protected":false},"author":21,"featured_media":6176,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[370],"tags":[444],"class_list":["post-6180","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-c-17","tag-parallel-stl"],"_links":{"self":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/6180","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=6180"}],"version-history":[{"count":1,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/6180\/revisions"}],"predecessor-version":[{"id":6705,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/posts\/6180\/revisions\/6705"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media\/6176"}],"wp:attachment":[{"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/media?parent=6180"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/categories?post=6180"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.modernescpp.com\/index.php\/wp-json\/wp\/v2\/tags?post=6180"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}