{"id":109228,"date":"2024-01-03T07:00:00","date_gmt":"2024-01-03T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=109228"},"modified":"2024-01-02T21:00:14","modified_gmt":"2024-01-03T05:00:14","slug":"20240103-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240103-00\/?p=109228","title":{"rendered":"Why doesn&#8217;t my code compile when I change a <CODE>shared_ptr&lt;T&gt;(p)<\/CODE> to an equivalent <CODE>make_shared&lt;T&gt;(p)<\/CODE>?"},"content":{"rendered":"<p>Today, we&#8217;re going to learn how to read C++ error messages.<\/p>\n<p>I was providing code review feedback, and one of the changes I suggested was to change<\/p>\n<pre>    auto widget = std::shared_ptr&lt;Widget&gt;(new Widget(this));\r\n<\/pre>\n<p>to<\/p>\n<pre>    auto widget = std::make_shared&lt;Widget&gt;(this);\r\n<\/pre>\n<p>This change solves a few problems.<\/p>\n<ul>\n<li>It&#8217;s less typing and avoids repeating the name <code>Widget<\/code>.<\/li>\n<li>It follows the C++ core guideline <a href=\"https:\/\/isocpp.github.io\/CppCoreGuidelines\/CppCoreGuidelines#r11-avoid-calling-new-and-delete-explicitly\"> R.11: Avoid calling <code>new<\/code> and <code>delete<\/code> explicitly<\/a>.<\/li>\n<li>It allows the control block to be combined with the <code>Widget<\/code>, avoiding an extra allocation. (See <a href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230815-00\/?p=108602\"> Inside STL: The <code>shared_ptr<\/code> constructor vs <code>make_shared<\/code><\/a>.)<\/li>\n<\/ul>\n<p>However, when the developer tried the alternative, a big cascade of errors was the result.<\/p>\n<pre style=\"white-space: pre-wrap;\">memory(874): error C2443: 'type cast': conversion from '_Ty' to 'IWidgetCallback *' exists, but is inaccessible\r\n    with\r\n    [\r\n        _Ty = WidgetContainer *\r\n    ] (compiling source file widgetcontainer.cpp)\r\nmemory(973): note: see reference to function template instantiation 'std::_Ref_count_obj&lt;_Ty&gt;::_Ref_count_obj&lt;WidgetContainer*&gt;(_V0_t &amp;&amp;)' being compiled\r\n    with\r\n    [\r\n        _Ty = Widget,\r\n        _V0_t = WidgetContainer *\r\n    ] (compiling source file widgetcontainer.cpp)\r\nwidgetcontainer.cpp(288): note: see reference to function template instantiation 'std::shared_ptr&lt;_Ty&gt; std::make_shared&lt;Widget, WidgetContainer*&gt;(_V0_t &amp;&amp;)' being compiled\r\n    with\r\n    [\r\n        _Ty = Widget,\r\n        _V0_t = WidgetContainer *\r\n    ]\r\n<\/pre>\n<p>What does this all mean?<\/p>\n<p>In general, C++ error messages put the most useful information at the start and at the end. All the stuff in the middle is to show you how the start and end got connected, but the important stuff usually isn&#8217;t in the middle.<\/p>\n<p>The Microsoft Visual C++ compiler puts the immediate problem at the start and works outward toward the code that triggered the problem.<\/p>\n<p>The last line points us at line 288 of <code>widgetcontainer.cpp<\/code>, and that&#8217;s the line that we wrote that the compiler is upset about:<\/p>\n<pre>    auto widget = std::make_shared&lt;Widget&gt;(this);\r\n<\/pre>\n<p>Okay, so let&#8217;s look at the other end of the error output, the top. After doing the substitution of <code>_Ty<\/code>, you get<\/p>\n<pre style=\"white-space: pre-wrap;\">memory(874): error C2443: 'type cast': conversion from 'WidgetContainer *' to 'IWidgetCallback *' exists, but is inaccessible\r\n<\/pre>\n<p>The problem is that somebody has a <code>WidgetContainer *<\/code> and wants to convert it to a <code>IWidgetContainer *<\/code>. The conversion exists but is inaccessible. What does this mean?<\/p>\n<p>In C++, there are three access levels: <code>public<\/code>, <code>protected<\/code>, and <code>private<\/code>.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Access<\/th>\n<th>From same class<\/th>\n<th>From same class<br \/>\nor derived class<\/th>\n<th>From anywhere<br \/>\nelse<\/th>\n<\/tr>\n<tr>\n<td><code>public<\/code><\/td>\n<td>Accessible<\/td>\n<td>Accessible<\/td>\n<td>Accessible<\/td>\n<\/tr>\n<tr>\n<td><code>protected<\/code><\/td>\n<td>Accessible<\/td>\n<td>Accessible<\/td>\n<td>Inaccessible<\/td>\n<\/tr>\n<tr>\n<td><code>private<\/code><\/td>\n<td>Accessible<\/td>\n<td>Inaccessible<\/td>\n<td>Inaccessible<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>We see from looking at the table that if If something is inaccessible, it means that it was declared as <code>private<\/code> and is being used outside the class, or it is <code>protected<\/code> and is being used outside the class or a derived class.<\/p>\n<p>So let&#8217;s see what the conversion&#8217;s access specifier is.<\/p>\n<pre>class WidgetContainer : IWidgetCallback\r\n{\r\n    \u27e6 ... \u27e7\r\n};\r\n<\/pre>\n<p>The default access specifier for a <code>class<\/code> is <code>private<\/code>, so <code>IWidgetCallback<\/code> is a private base class of <code>WidgetContainer<\/code>, and only the class itself can convert a pointer to itself into a pointer to the base class.<\/p>\n<p>And that is consistent with the call site, which happens to be the <code>Widget\u00adContainer<\/code> constructor.<\/p>\n<pre>WidgetContainer::WidgetContainer()\r\n{\r\n    \u27e6 ... \u27e7\r\n\r\n    auto widget = std::make_shared&lt;Widget&gt;(this);\r\n\r\n    \u27e6 ... \u27e7\r\n}\r\n<\/pre>\n<p>When this code was written as<\/p>\n<pre>    auto widget = std::shared_ptr&lt;Widget&gt;(new Widget(this));\r\n<\/pre>\n<p>the conversion of <code>this<\/code> from a <code>Widget\u00adContainer*<\/code> to a <code>IWidget\u00adCallback*<\/code> happened inside the context of a <code>Widget\u00adContainer<\/code>, so the conversion was allowed.<\/p>\n<p>But changing to <code>make_shared<\/code> means that the parameter is passed into <code>make_shared<\/code> as a <code>Widget\u00adContainer*<\/code>, and the conversion to a <code>IWidget\u00adCallback*<\/code> doesn&#8217;t happe until the <code>Widget<\/code> constructor is reached, which is not inside <code>Widget\u00adContainer<\/code>.<\/p>\n<p>So what can we do?<\/p>\n<p>One approach is to force the conversion to happen in a context in which it is allowed.<\/p>\n<pre>    auto widget = std::make_shared&lt;Widget&gt;(\r\n        <span style=\"border: solid 1px currentcolor;\">static_cast&lt;IWidgetCallback*&gt;<\/span>(this));\r\n<\/pre>\n<p>This pre-converts the <code>Widget\u00adContainer*<\/code> to a <code>IWidget\u00adCallback*<\/code> so that <code>make_shared<\/code> doesn&#8217;t have to do it.<\/p>\n<p>But I suggested checking whether the private-ness of <code>WidgetContainer<\/code>&#8216;s use of <code>IWidgetCallback<\/code> as a base class was intentional. Maybe the <code>public<\/code> keyword was left off by mistake.<\/p>\n<p>After some investigation, it seems that there was no need for <code>IWidgetCallback<\/code> to be a private base class, so we made it public, and that fixed it.<\/p>\n<p><b>Bonus chatter<\/b>: The clang compiler also puts the immediate problem at the start and works outward toward the code that triggered the problem.<\/p>\n<pre style=\"white-space: pre-wrap;\">\/\/ clang\r\nIn file included from widgetcontainer.cpp:5:\r\nIn file included from memory:66:\r\nIn file included from stl_tempbuf.h:61:\r\nstl_construct.h:115:4: error: no matching function for call to 'construct_at'\r\n  115 |           std::construct_at(__p, std::forward&lt;_Args&gt;(__args)...);\r\n      |           ^~~~~~~~~~~~~~~~~\r\nalloc_traits.h:660:9: note: in instantiation of function template specialization 'std::_Construct&lt;Widget, WidgetContainer *&gt;' requested here\r\n  660 |         { std::_Construct(__p, std::forward&lt;_Args&gt;(__args)...); }\r\n      |                ^\r\nshared_ptr_base.h:604:30: note: in instantiation of function template specialization 'std::allocator_traits&lt;std::allocator&lt;void&gt;&gt;::construct&lt;Widget, WidgetContainer *&gt;' requested here\r\n  604 |           allocator_traits&lt;_Alloc&gt;::construct(__a, _M_ptr(),\r\n      |                                     ^\r\nshared_ptr_base.h:972:6: note: in instantiation of function template specialization 'std::_Sp_counted_ptr_inplace&lt;Widget, std::allocator&lt;void&gt;, __gnu_cxx::_S_atomic&gt;::_Sp_counted_ptr_inplace&lt;WidgetContainer *&gt;' requested here\r\n  972 |             _Sp_cp_type(__a._M_a, std::forward&lt;_Args&gt;(__args)...);\r\n      |             ^\r\nshared_ptr_base.h:1712:14: note: in instantiation of function template specialization 'std::__shared_count&lt;&gt;::__shared_count&lt;Widget, std::allocator&lt;void&gt;, WidgetContainer *&gt;' requested here\r\n 1712 |         : _M_ptr(), _M_refcount(_M_ptr, __tag, std::forward&lt;_Args&gt;(__args)...)\r\n      |                     ^\r\nshared_ptr.h:464:4: note: in instantiation of function template specialization 'std::__shared_ptr&lt;Widget&gt;::__shared_ptr&lt;std::allocator&lt;void&gt;, WidgetContainer *&gt;' requested here\r\n  464 |         : __shared_ptr&lt;_Tp&gt;(__tag, std::forward&lt;_Args&gt;(__args)...)\r\n      |           ^\r\nshared_ptr.h:1009:14: note: in instantiation of function template specialization 'std::shared_ptr&lt;Widget&gt;::shared_ptr&lt;std::allocator&lt;void&gt;, WidgetContainer *&gt;' requested here\r\n 1009 |       return shared_ptr&lt;_Tp&gt;(_Sp_alloc_shared_tag&lt;_Alloc&gt;{__a},\r\n      |              ^\r\nwidgetcontainer.cpp:288:16: note: in instantiation of function template specialization 'std::make_shared&lt;Widget, WidgetContainer *&gt;' requested here\r\n   15 |         auto widget = std::make_shared&lt;Widget&gt;(this);\r\n      |                            ^\r\nstl_construct.h:94:5: note: candidate template ignored: substitution failure [with _Tp = Widget, _Args = &lt;WidgetContainer *&gt;]: cannot cast 'WidgetContainer' to its private base class 'IWidgetCallback'\r\n   96 |     construct_at(_Tp* __location, _Args&amp;&amp;... __args)\r\n      |     ^\r\n   97 |     noexcept(noexcept(::new((void*)0) _Tp(std::declval&lt;_Args&gt;()...)))\r\n   98 |     -&gt; decltype(::new((void*)0) _Tp(std::declval&lt;_Args&gt;()...))\r\n      |                                     ~~~\r\nstl_construct.h:119:29: error: cannot cast 'WidgetContainer' to its private base class 'IWidgetCallback'\r\n  119 |       ::new((void*)__p) _Tp(std::forward&lt;_Args&gt;(__args)...);\r\n      |                             ^\r\nwidgetcontainer.cpp:30:25: note: implicitly declared private here\r\n   10 | class WidgetContainer : IWidgetCallback\r\n      |                         ^~~~~~~~~~~~~~~\r\n<\/pre>\n<p>The clang compiler is nice enough to explain why the conversion is inaccessible: &#8220;Implicitly declared private here.&#8221;<\/p>\n<p>The gcc compiler goes in the opposite order, starting with the code that triggered the problem and working toward the code that encountered the immediate problem:<\/p>\n<pre style=\"white-space: pre-wrap;\">\/\/ gcc\r\nIn file included from stl_tempbuf.h:61,\r\n                 from memory:66,\r\n                 from widgetcontainer.cpp:5:\r\nstl_construct.h: In instantiation of 'constexpr void std::_Construct(_Tp*, _Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}]':\r\nalloc_traits.h:660:19:   required from 'static constexpr void std::allocator_traits&lt;std::allocator&lt;void&gt; &gt;::construct(allocator_type&amp;, _Up*, _Args&amp;&amp;&amp; ...) [with _Up = Widget; _Args = {WidgetContainer*}; allocator_type = std::allocator&lt;void&gt;]'\r\nshared_ptr_base.h:604:39:   required from 'std::_Sp_counted_ptr_inplace&lt;_Tp, _Alloc, _Lp&gt;::_Sp_counted_ptr_inplace(_Alloc, _Args&amp;&amp; ...) [with _Args = {WidgetContainer*}; _Tp = Widget; _Alloc = std::allocator&lt;void&gt;; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:971:16:   required from 'std::__shared_count&lt;_Lp&gt;::__shared_count(_Tp*&amp;, std::_Sp_alloc_shared_tag&lt;_Alloc&gt;, _Args&amp;&amp; ...) [with _Tp = Widget; _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:1712:14:   required from 'std::__shared_ptr&lt;_Tp, _Lp&gt;::__shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; _Tp = Widget; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr.h:464:59:   required from 'std::shared_ptr&lt;_Tp&gt;::shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; _Tp = Widget]'\r\nshared_ptr.h:1009:14:   required from 'std::shared_ptr&lt;std::_NonArray&lt;_Tp&gt; &gt; std::make_shared(_Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}; _NonArray&lt;_Tp&gt; = Widget]'\r\n&lt;source&gt;:15:35:   required from here\r\nstl_construct.h:115:28: error: no matching function for call to 'construct_at(Widget*&amp;, WidgetContainer*)'\r\n  115 |           std::construct_at(__p, std::forward&lt;_Args&gt;(__args)...);\r\n      |           ~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\nstl_construct.h:94:5: note: candidate: 'template&lt;class _Tp, class ... _Args&gt; constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&amp;&amp; ...)'\r\n   94 |     construct_at(_Tp* __location, _Args&amp;&amp;... __args)\r\n      |     ^~~~~~~~~~~~\r\nstl_construct.h:94:5: note:   template argument deduction\/substitution failed:\r\nstl_construct.h: In substitution of 'template&lt;class _Tp, class ... _Args&gt; constexpr decltype (::new(void*(0)) _Tp) std::construct_at(_Tp*, _Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}]':\r\nstl_construct.h:115:21:   required from 'constexpr void std::_Construct(_Tp*, _Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}]'\r\nalloc_traits.h:660:19:   required from 'static constexpr void std::allocator_traits&lt;std::allocator&lt;void&gt; &gt;::construct(allocator_type&amp;, _Up*, _Args&amp;&amp; ...) [with _Up = Widget; _Args = {WidgetContainer*}; allocator_type = std::allocator&lt;void&gt;]'\r\nshared_ptr_base.h:604:39:   required from 'std::_Sp_counted_ptr_inplace&lt;_Tp, _Alloc, _Lp&gt;::_Sp_counted_ptr_inplace(_Alloc, _Args&amp;&amp; ...) [with _Args = {WidgetContainer*}; _Tp = Widget; _Alloc = std::allocator&lt;void&gt;; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:971:16:   required from 'std::__shared_count&lt;_Lp&gt;::__shared_count(_Tp*&amp;, std::_Sp_alloc_shared_tag&lt;_Alloc&gt;, _Args&amp;&amp; ...) [with _Tp = Widget; _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:1712:14:   required from 'std::__shared_ptr&lt;_Tp, _Lp&gt;::__shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; _Tp = Widget; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr.h:464:59:   required from 'std::shared_ptr&lt;_Tp&gt;::shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; _Tp = Widget]'\r\nshared_ptr.h:1009:14:   required from 'std::shared_ptr&lt;std::_NonArray&lt;_Tp&gt; &gt; std::make_shared(_Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}; _NonArray&lt;_Tp&gt; = Widget]'\r\nwidgetcontainer.cpp:288:35: required from here\r\nstl_construct.h:96:17: error: 'IWidgetCallback' is an inaccessible base of 'WidgetContainer'\r\n   96 |     -&gt; decltype(::new((void*)0) _Tp(std::declval&lt;_Args&gt;()...))\r\n      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ \r\nstl_construct.h: In instantiation of 'constexpr void std::_Construct(_Tp*, _Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}]':\r\nalloc_traits.h:660:19:   required from 'static constexpr void std::allocator_traits&lt;std::allocator&lt;void&gt; &gt;::construct(allocator_type&amp;, _Up*, _Args&amp;&amp; ...) [with _Up = Widget; _Args = {WidgetContainer*}; allocator_type = std::allocator&lt;void&gt;]'\r\nshared_ptr_base.h:604:39:   required from 'std::_Sp_counted_ptr_inplace&lt;_Tp, _Alloc, _Lp&gt;::_Sp_counted_ptr_inplace(_Alloc, _Args&amp;&amp; ...) [with _Args = {WidgetContainer*}; _Tp = Widget; _Alloc = std::allocator&lt;void&gt;; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:971:16:   required from 'std::__shared_count&lt;_Lp&gt;::__shared_count(_Tp*&amp;, std::_Sp_alloc_shared_tag&lt;_Alloc&gt;, _Args&amp;&amp; ...) [with _Tp = Widget; _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr_base.h:1712:14:   required from 'std::__shared_ptr&lt;_Tp, _Lp&gt;::__shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; _Tp = Widget; __gnu_cxx::_Lock_policy _Lp = __gnu_cxx::_S_atomic]'\r\nshared_ptr.h:464:59:   required from 'std::shared_ptr&lt;_Tp&gt;::shared_ptr(std::_Sp_alloc_shared_tag&lt;_Tp&gt;, _Args&amp;&amp; ...) [with _Alloc = std::allocator&lt;void&gt;; _Args = {WidgetContainer*}; _Tp = Widget]'\r\nshared_ptr.h:1009:14:   required from 'std::shared_ptr&lt;std::_NonArray&lt;_Tp&gt; &gt; std::make_shared(_Args&amp;&amp; ...) [with _Tp = Widget; _Args = {WidgetContainer*}; _NonArray&lt;_Tp&gt; = Widget]'\r\nwidgetcontainer.cpp:288:35: required from here\r\nstl_construct.h:119:7: error: 'IWidgetCallback' is an inaccessible base of 'WidgetContainer'\r\n  119 |       ::new((void*)__p) _Tp(std::forward&lt;_Args&gt;(__args)...);\r\n      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\r\n<\/pre>\n<p>There it is: &#8220;<code>IWidget\u00adCallback<\/code> is an inaccessible base of <code>Widget\u00adContainer<\/code>.&#8221;<\/p>\n<p>(If this looks familiar, it&#8217;s because it&#8217;s basically the same thing we saw before when we learned that <a title=\"Sometimes perfect forwarding can be too perfect: Lazy conversion is lazy\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221123-00\/?p=107443\"> perfect forwarding can sometimes be too perfect<\/a>.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>It depends on who is doing the parameter conversion.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-109228","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>It depends on who is doing the parameter conversion.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109228","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=109228"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/109228\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=109228"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=109228"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=109228"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}