{"id":9205,"date":"2016-06-07T10:17:34","date_gmt":"2016-06-07T17:17:34","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/vcblog\/?p=9205"},"modified":"2019-02-18T18:04:47","modified_gmt":"2019-02-18T18:04:47","slug":"expression-sfinae-improvements-in-vs-2015-update-3","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/cppblog\/expression-sfinae-improvements-in-vs-2015-update-3\/","title":{"rendered":"Expression SFINAE improvements in VS 2017 RC"},"content":{"rendered":"<p><em>This post written by Xiang Fan, Andrew Pardoe, and Gabriel Dos Reis<\/em><\/p>\n<p><strong>This post was updated to reflect progress we&#8217;ve made through VS 2017 RC since VS 2015 Update 3.<\/strong> <\/p>\n<p>Throughout the VS 2015 cycle we&#8217;ve been focusing on the quality of our expression SFINAE implementation. Because expression SFINAE issues can be subtle and complex we&#8217;ve been using popular libraries such as <a href=\"http:\/\/www.boost.org\/\">Boost<\/a> and <a href=\"https:\/\/github.com\/microsoft\/Range-V3-VS2015\/\">Microsoft&#8217;s fork of Range-v3<\/a>\u00a0to validate our implementation and find remaining bugs. As we shift the compiler team&#8217;s focus to Visual Studio 2017 release we&#8217;re excited to tell you about the improvements we&#8217;ve made in correctly parsing expression SFINAE.<\/p>\n<p>With VS 2015 Update 3 we told you that <a href=\"http:\/\/www.boost.org\/\">Boost<\/a> compiles correctly with MSVC without defining the macro <code>BOOST_NO_SFINAE_EXPR<\/code>. A few libraries in Boost still didn&#8217;t compile and Range-v3 support was incomplete. With Visual Studio 2017 Release Candidate Visual C++\u00a0can now compile <a href=\"http:\/\/boost-spirit.com\/home\/category\/spirit-x3\/\">Boost Spirit X3<\/a>, <a href=\"http:\/\/bolero-murakami.github.io\/Sprout\/\">Sprout<\/a>, and <a href=\"https:\/\/github.com\/microsoft\/Range-V3-VS2015\">Range-v3<\/a>. We&#8217;ve made fantastic progress so far and will soon have a complete and correct implementation of expression SFINAE.<\/p>\n<p>Our next focus is Boost Hana where over 70% of the tests pass so far with some source workarounds and some compiler fixes.We&#8217;re at the point where many of the bugs that we&#8217;re discovering aren&#8217;t actually expression SFINAE bugs. We&#8217;ve uncovered issues with <code>constexpr<\/code>, generic lambdas, pack expansions, variadic macros, special member functions, parsing problems, and other issues. Some of these issues look like expression SFINAE issues but turn out to be SFINAE dependencies on other areas. We&#8217;ve fixed about 20 issues, three of which were SFINAE issues, and have about 35 left to fix in Hana. We look forward to Hana compiling cleanly without workarounds during the VS 2017 cycle.<\/p>\n<h3>What is expression SFINAE?<\/h3>\n<p>SFINAE is an acronym for \u201c<strong>s<\/strong>ubstitution <strong>f<\/strong>ailure <strong>i<\/strong>s <strong>n<\/strong>ot <strong>a<\/strong>n <strong>e<\/strong>rror.\u201d It is derived from an arcane process used by C++ compilers during overload resolution.\u00a0 At its core, the idea is quite simple: <strong><em>if a candidate function template\u2019s specialization would lead to an ill-formed (compiler-internal) declaration of that specialization, just silently ignore that template as is if the compiler has never seen it<\/em><\/strong>. In another words, the compiler will pretend that wasn\u2019t the template it was looking for.\u00a0 It is an old notion that has been part of C++ since it\u2019s the C++98 release. In that version of C++, the condition for \u201cill-formed compiler-internal declaration of a function template specialization\u201d was specified for <strong>types<\/strong> only.<\/p>\n<p>With the advent of type query features such as <code>decltype<\/code> and <code>auto<\/code>, the validity of a function type now entails the validity of expressions, since whether <code>decltype(expr)<\/code> is a valid type depends on whether the operand <code>expr<\/code> is well-formed.\u00a0 Hence the term \u201cExpression SFINAE\u201d. \u00a0It is a little bit more involved because now the compiler has to perform overload resolution including potentially unbounded evaluation of constant expressions while it is checking whether a type makes sense.<\/p>\n<h3>Improvements since Visual Studio 2015 Update 3<\/h3>\n<p>We now correctly compile code that constructs temporary objects as Range-v3 does extensively:<\/p>\n<pre class=\"prettyprint\">\n\t\t#include &lt;type_traits&gt;\n\t\t\n\t\ttemplate&lt;typename T, std::enable_if_t&lt;std::is_integral&lt;T&gt;{}&gt; * = nullptr&gt;\n\t\tchar f(T *);\n\t\t\n\t\ttemplate&lt;typename T&gt;\n\t\tshort f(...);\n\t\t\n\t\tint main()\n\t\t{\n\t\t\tstatic_assert(sizeof(f&lt;int&gt;(nullptr)) == sizeof(char), &quot;fail&quot;);\n\t\t\tstatic_assert(sizeof(f&lt;int *&gt;(nullptr)) == sizeof(short), &quot;fail&quot;);\n\t\t}\n<\/pre>\n<p>We&#8217;ve also improved access checks for SFINAE which are illustrated in this code sample:<\/p>\n<pre class=\"prettyprint\">\n\t\ttemplate &lt;typename T&gt; class S {\n\t\tprivate:\n\t\t\ttypedef T type;\n\t\t};\n\t\t\n\t\ttemplate &lt;typename T&gt; class S&lt;T *&gt; {\n\t\tpublic:\n\t\t\ttypedef T type;\n\t\t};\n\t\t\n\t\ttemplate &lt;typename T, typename S&lt;T&gt;::type * = nullptr&gt;\n\t\tchar f(T);\n\t\t\n\t\ttemplate&lt;typename T&gt;\n\t\tshort f(...);\n\t\t\n\t\tint main()\n\t\t{\n\t\t\tstatic_assert(sizeof(f&lt;int&gt;(0)) == 2, &quot;fail&quot;); \/\/ fails in VS2015\n\t\t\tstatic_assert(sizeof(f&lt;int *&gt;(nullptr)) == 1, &quot;fail&quot;);\n\t\t}\n<\/pre>\n<p>Lastly, we&#8217;ve improved support for <code>void_t<\/code> when used inside of a typename as found in Boost Hana:<\/p>\n<pre class=\"prettyprint\">\n\t\ttemplate&lt;typename T, typename U&gt;\n\t\tstruct std_common_type {};\n\t\t\n\t\ttemplate&lt;typename T&gt;\n\t\tstruct std_common_type&lt;T, T&gt; { using type = T; };\n\t\t\n\t\ttemplate&lt;typename T, typename U&gt;\n\t\tstruct is_same { static const bool value = false; };\n\t\t\n\t\ttemplate&lt;typename T&gt;\n\t\tstruct is_same&lt;T, T&gt; { static const bool value = true; };\n\t\t\n\t\ttemplate&lt;bool, typename T&gt;\n\t\tstruct enable_if {};\n\t\t\n\t\ttemplate&lt;typename T&gt;\n\t\tstruct enable_if&lt;true, T&gt; { using type = T; };\n\t\t\n\t\ttemplate&lt;typename...&gt; using void_t = void;\n\t\t\n\t\ttemplate &lt;typename T, typename U = T, typename = void&gt;\n\t\tstruct EqualityComparable1 { static const bool value = false; };\n\t\t\n\t\ttemplate &lt;typename T, typename U&gt;\n\t\tstruct EqualityComparable1&lt;T, U, typename enable_if&lt;!is_same&lt;T, U&gt;::value, void_t&lt;typename std_common_type&lt;T, U&gt;::type&gt;&gt;::type&gt;\n\t\t{\n\t\t\tstatic const bool value = true;\n\t\t};\n\t\t\n\t\ttemplate &lt;typename T, typename U = T, typename = void&gt;\n\t\tstruct EqualityComparable2 { static const bool value = false; };\n\t\t\n\t\ttemplate &lt;typename T, typename U&gt;\n\t\tstruct EqualityComparable2&lt;T, U, void_t&lt;typename std_common_type&lt;T, U&gt;::type&gt;&gt;\n\t\t{\n\t\t\tstatic const bool value = true;\n\t\t};\n\t\t\n\t\tvoid f()\n\t\t{\n\t\t\tstruct S1 {};\n\t\t\tstruct S2 {};\n\t\t\tstatic_assert(!EqualityComparable1&lt;S1, S2&gt;::value, &quot;fail&quot;); \/\/ fails in VS2015\n\t\t\tstatic_assert(!EqualityComparable2&lt;S1, S2&gt;::value, &quot;fail&quot;);\n\t\t}\n<\/pre>\n<h3>Improvements since Visual Studio 2015 Update 2<\/h3>\n<p>Continued improvements in the quality of our expression SFINAE implementation enabled our Standard Template Library to begin using it in VS 2015 Update 2. Expression SFINAE is used in our implementations of <code>std::function<\/code> and <code>result_of<\/code>.<\/p>\n<h3>Improvements since Visual Studio 2015 Update 1<\/h3>\n<p>Because we&#8217;re now generating parse trees for\u00a0<code>decltype<\/code> expressions a number of patterns work correctly in Update 3.<\/p>\n<ul>\n<li>We&#8217;ve implemented checking for dependent expression using the new parse tree in the compiler. That fixes <a href=\"https:\/\/connect.microsoft.com\/VisualStudio\/feedback\/details\/1660945\/vs-2015-rejects-chromium-template-code-which-vs-2013-gcc-and-clang-all-accept\">this Connect issue reported for a failure compiling Chromium<\/a>.<\/li>\n<li>We&#8217;ve implemented ability to distinguish different expressions inside decltype using parse tree. Here&#8217;s an example simplified from the Boost thread library:\n<pre class=\"prettyprint\">\ntemplate&lt;class T&gt;\nstruct remove_reference\n{\n    typedef T type;\n};\n\ntemplate&lt;class T&gt;\ninline T&amp;&amp; forward(typename remove_reference&lt;T&gt;::type&amp; t)\n{\n\treturn static_cast&lt;T&amp;&amp;&gt;(t);\n}\n\ntemplate&lt;class T&gt; \ninline T&amp;&amp; forward(typename remove_reference&lt;T&gt;::type&amp;&amp; t)\n{\n\treturn static_cast&lt;T&amp;&amp;&gt;(t);\n}\n\ntemplate &lt;class Fp, class A0, class ...Args&gt;\ninline auto invoke(Fp &amp;&amp; f, A0 &amp;&amp; a0, Args &amp;&amp; ...args)\n-&gt; decltype((forward&lt;A0&gt;(a0).*f)(forward&lt;Args&gt;(args)...))\n{\n\treturn (forward&lt;A0&gt;(a0).*f)(forward&lt;Args&gt;(args)...);\n}\n\ntemplate &lt;class Fp, class A0, class ...Args&gt;\ninline auto invoke(Fp &amp;&amp; f, A0 &amp;&amp; a0, Args &amp;&amp; ...args)\n-&gt; decltype(((*forward&lt;A0&gt;(a0)).*f)(forward&lt;Args&gt;(args)...))\n{\n\treturn ((*forward(a0)).*f)(forward(args)...);\n}\n<\/pre>\n<\/li>\n<li>A couple of test cases simplified from Range-v3 now work.\n<pre class=\"prettyprint\">\nint f(int *);\n\t\t\nnamespace N {\n\ttemplate&lt;typename T&gt; T val();\n\n\ttemplate&lt;typename T&gt; using void_t = void;\n\t\t\n\ttemplate&lt;typename T, typename = void&gt; struct trait {};\n\ttemplate&lt;typename T&gt; struct trait&lt;T, void_t&lt;decltype(f(val&lt;T&gt;()))&gt;&gt; {\n\t\ttypedef decltype(f(val&lt;T&gt;())) type;\n\t};\n}\n\t\t\nN::trait&lt;int *&gt;::type t1;\n\t\t\nstruct S {\n\ttemplate&lt;typename T&gt; static T val();\n\n\ttemplate&lt;typename T&gt; using void_t = void;\n\n\ttemplate&lt;typename T, typename = void&gt; struct trait {};\n\ttemplate&lt;typename T&gt; struct trait&lt;T, void_t&lt;decltype(f(val&lt;T&gt;()))&gt;&gt; {\n\t\ttypedef decltype(f(val&lt;T&gt;())) type;\n\t};\n};\n\t\t\nS::trait&lt;int *&gt;::type t2;\n<\/pre>\n<\/li>\n<li>Also, this example:\n<pre class=\"prettyprint\">\nint g;\n\t\t\ntemplate&lt;typename T&gt;\nusing void_t = void;\n\t\t\ntemplate&lt;typename T, typename = void&gt;\nstruct S1 {};\n\t\t\ntemplate&lt;typename T&gt;\nstruct S1&lt;T, void_t&lt;decltype(g + T{}) &gt;&gt; {};\n\t\t\nstruct S2 {\n\tint *g;\n\tauto f() -&gt; decltype(S1&lt;int&gt;());\n};\n<\/pre>\n<\/li>\n<\/ul>\n<h3>Moving away from the token stream parser<\/h3>\n<p>A lot of the improvements you&#8217;re seeing in expression SFINAE support and other areas comes from work we&#8217;re doing to rejuvenate our old compiler. The Visual C++ compiler has been around for over thirty years&#8211;long before C++ had templates. This means that we&#8217;re now working around design decisions that once made sense.<\/p>\n<p>Visual C++ traditionally took a token stream-based approach to parsing templates. When we\u00a0encounter a template in your code we capture its body as a sequence of tokens without any attempt to understand what the tokens mean. Storing the body as a stream of tokens makes analysis of trailing return types containing decltype-specifiers imperfect, especially in SFINAE contexts.<\/p>\n<p>We have now implemented a recursive-descent parser that generates high level unbound trees for expressions and employed this to analyze the expression argument of <code>decltype<\/code> in a much more precise way, allowing a better implementation of expression SFINAE. The recursive descent parser is a work in progress; currently, it can parse only C++ expressions but we&#8217;re going to soon expand it to parse the entire C++ syntax and make it the basis for implementing features such as two-phase name lookup. These features have been almost impossible to implement with the token stream-based parser. As work proceeds, the remaining gaps in expression SFINAE will also be filled.<\/p>\n<p>If you&#8217;d like to read more about the changes we&#8217;re making to the parser you can find more in this blog post: <a href=\"https:\/\/blogs.msdn.microsoft.com\/vcblog\/2015\/09\/25\/rejuvenating-the-microsoft-cc-compiler\/\">Rejuvenating the Microsoft C\/C++ Compiler<\/a>.<\/p>\n<h3>Known issues as of VS 2017 Release Candidate<\/h3>\n<p>You may encounter the following known issues when using expression SFINAE in the Visual C++ compiler as of VS 2017 Release Candidate.<\/p>\n<ul>\n<li>A couple of issues impact input to SFINAE:\n<ul>\n<li>Some uses of <code>constexpr<\/code> lead to incorrect specializations. The parser does semantic analysis aggressively even when the template argument is dependent. Thus it will try to specialize <code>f(T{})<\/code> in the below example and will fail. This leaves a wrong specialization with a <code>nullptr<\/code> expression (or a dummy expression) as the template non-type argument. Any further usage of the template will fail.The new parser only does semantic analysis on non-dependent expressions. We are progressively moving the parsing of template arguments to the new parser.Compiling this code:\n<pre class=\"prettyprint\">\t\t\n\t\ttemplate&lt;bool&gt; struct S {};\n\t\t\n\t\ttemplate&lt;typename T&gt; constexpr bool f(T) { return true; }\n\t\t\n\t\ttemplate&lt;typename T&gt; void g(S&lt;f(T{})&gt;) {}\n\t\ttemplate&lt;typename T&gt; void g(S&lt;f(T{1})&gt;) {}\n<\/pre>\n<p>Currently produces this error message:<\/p>\n<pre>error C2995: 'void g(S)': function template has already been defined<\/pre>\n<p>One possible workaround is to use a variable template:<\/p>\n<pre class=\"prettyprint\">\n\t\ttemplate&lt;bool&gt; struct S {};\n\t\t\n\t\ttemplate&lt;typename T&gt; constexpr bool f(T) { return true; }\n\t\t\n\t\ttemplate&lt;typename T&gt; constexpr auto g_value1 = f(T{});\n\t\ttemplate&lt;typename T&gt; constexpr auto g_value2 = f(T{1});\n\t\t\n\t\ttemplate&lt;typename T&gt; void g(S&lt;g_value1&lt;T&gt;&gt;) {}\n\t\ttemplate&lt;typename T&gt; void g(S&lt;g_value2&lt;T&gt;&gt;) {}\n<\/pre>\n<\/li>\n<li>Some uses of expressions inside <code>decltype<\/code> cannot be properly distinguished. In VS2015 RTM, we store expressions inside <code>decltype<\/code> as tokens and we can&#8217;t distinguish expression in it, so any <code>decltype<\/code> is considered the same.We have started to move parsing of expressions inside <code>decltype<\/code> to the new parser since VS 2015 Update 1. With the new parser we&#8217;re able to distinguish some kinds of expressions. However, symbols are not bound yet so the compiler can&#8217;t distinguish between <code>T<\/code> and <code>U<\/code>. This means you are not able to define the two overloads in the following code sample. When we start to bind symbols in the AST tree generated by the new parser the compiler will be able to compare them.\n<pre class=\"prettyprint\">\ntemplate&lt;typename T, typename U&gt; void f(decltype(T{})) {}\ntemplate&lt;typename T, typename U&gt; void f(decltype(U{})) {}\n<\/pre>\n<p>Currently produces this error message:<\/p>\n<pre>error C2995: 'void f(unknown-type)': function template has already been defined<\/pre>\n<p>One possible workaround is to use a helper class to create a unique type, as shown in this code:<\/p>\n<pre class=\"prettyprint\">\n\t\ttemplate&lt;typename T, typename Unique&gt; struct helper { using type = T; };\n\t\t\n\t\tstruct Unique1 {};\n\t\tstruct Unique2 {};\n\t\t\n\t\ttemplate&lt;typename T, typename U&gt; void f(typename helper&lt;decltype(T{}), Unique1&gt;::type) {}\n                template&lt;typename T, typename U&gt; void f(typename helper&lt;decltype(U{}), Unique2&gt;::type) {}\n<\/pre>\n<\/li>\n<\/ul>\n<\/li>\n<li>A couple of issues impact type replacement during SFINAE.\n<ul>\n<li>Pack expansion: If the parameter pack is used in dependent expression or <code>decltype<\/code>, pack expansion may fail. Our current implementation of variadic template is based on tokens from the old parser so isn&#8217;t always able to handle arbitrary expressions. One example can be seen in the following code. If you use such expressions as part of the function declaration, then SFINAE won&#8217;t work correctly because pack expansion doesn&#8217;t happen. Identifying parameter packs and doing pack expansion will be much more robust once we move variadic templates to use the new parser&#8217;s parse tree.Compiling this code:\n<pre class=\"prettyprint\">\n\t\ttemplate&lt;bool...&gt;\n\t\tstruct S1 {\n\t\t\tstatic const bool value = true;\n\t\t};\n\t\t\n\t\ttemplate&lt;typename T&gt;\n\t\tconstexpr T value() { return{}; }\n\t\t\n\t\ttemplate &lt;typename Ys&gt;\n\t\tstruct S2 {\n\t\t\tYs ys;\n\t\t\ttemplate &lt;typename ...X&gt;\n\t\t\tconstexpr auto operator()(X const&amp; ...x) const {\n\t\t\t\treturn S1&lt;value&lt;decltype(ys + x)&gt;()...&gt;::value;\n\t\t\t}\n\t\t};\n\t\t\n\t\tvoid f() {\n\t\t\tS2&lt;int&gt; s;\n\t\t\ts(0, 1);\n\t\t}\n\n<\/pre>\n<p>Currently produces this error message:<\/p>\n<pre>error C3520: 'x': parameter pack must be expanded in this context<\/pre>\n<\/li>\n<li>Alias templates: Type replacement may fail if an alias template has dependent expression or <code>decltype<\/code> in it. Our current implementation of alias templates uses type replacement and reparsing of the token stream from the old parser. The latter is used for dependent expression and <code>decltype<\/code>, but the context in which the reparsing is done isn&#8217;t always correct.If you use this kind of alias templates in a SFINAE context the result are currently unpredictable \ud83d\ude42 Once we move alias template parsing to use the new parser we will no longer need to reparse the token stream, an operation that is sensitive to context and error-prone.Compiling this code:\n<pre class=\"prettyprint\">\n\t\ttemplate&lt;typename&gt; struct S {\n\t\t using type = int;\n\t\t};\n\t\t\n\t\ttemplate&lt;typename T&gt; using type1 = decltype(S&lt;T&gt;{});\n\t\t\n\t\ttemplate&lt;typename T&gt; using type2 = typename type1&lt;T&gt;::type;\n\t\ttype2&lt;int&gt; i;\n<\/pre>\n<p>Currently produces this error message:<\/p>\n<pre>error C2938: 'type2' : Failed to specialize alias template<\/pre>\n<p>A workaround to make this kind of alias templates work reliably in SFINAE context is to provide a helper class and use partial specialization for SFINAE purposes. This following code illustrates this workaround.<\/p>\n<pre class=\"prettyprint\">\n\t\ttemplate&lt;typename&gt; struct S {\n\t\t using type = int;\n\t\t};\n\t\t\n\t\ttemplate&lt;typename&gt;\n\t\tusing type1_void_t = void;\n\t\ttemplate&lt;typename, typename = void&gt; struct type1_helper {};\n\t\ttemplate&lt;typename T&gt; struct type1_helper&lt;T, type1_void_t&lt;decltype(S&lt;T&gt;{}) &gt;&gt; {\n\t\t\tusing type = decltype(S&lt;T&gt;{});\n\t\t};\n\t\t\n\t\ttemplate&lt;typename T&gt; using type1 = typename type1_helper&lt;T&gt;::type;\n\t\t\t\n\t\ttemplate&lt;typename T&gt; using type2 = typename type1&lt;T&gt;::type;\n\t\ttype2&lt;int&gt; i;\n<\/pre>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h3>Send us feedback!<\/h3>\n<p>As always, we welcome your feedback. For problems, let us know via the Report a Problem option, either from the installer or the Visual Studio IDE itself. For suggestions, let us know through <a href=\"https:\/\/visualstudio.uservoice.com\/forums\/121579-visual-studio-2015\/category\/30937-languages-c\">UserVoice<\/a>. And you can always reach us through e-mail at <a href=\"mailto:visualcpp@microsoft.com\">visualcpp@microsoft.com<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This post written by Xiang Fan, Andrew Pardoe, and Gabriel Dos Reis This post was updated to reflect progress we&#8217;ve made through VS 2017 RC since VS 2015 Update 3. Throughout the VS 2015 cycle we&#8217;ve been focusing on the quality of our expression SFINAE implementation. Because expression SFINAE issues can be subtle and complex [&hellip;]<\/p>\n","protected":false},"author":312,"featured_media":35994,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[140,248,323],"class_list":["post-9205","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-cplusplus","tag-c","tag-expression-sfinae","tag-sfinae"],"acf":[],"blog_post_summary":"<p>This post written by Xiang Fan, Andrew Pardoe, and Gabriel Dos Reis This post was updated to reflect progress we&#8217;ve made through VS 2017 RC since VS 2015 Update 3. Throughout the VS 2015 cycle we&#8217;ve been focusing on the quality of our expression SFINAE implementation. Because expression SFINAE issues can be subtle and complex [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/9205","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/users\/312"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/comments?post=9205"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/posts\/9205\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media\/35994"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/media?parent=9205"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/categories?post=9205"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/cppblog\/wp-json\/wp\/v2\/tags?post=9205"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}