{"id":902,"date":"2024-02-23T07:49:29","date_gmt":"2024-02-23T07:49:29","guid":{"rendered":"https:\/\/tbekk.com\/devstream\/?p=902"},"modified":"2024-02-23T07:50:11","modified_gmt":"2024-02-23T07:50:11","slug":"constexpr-and-consteval-functions","status":"publish","type":"post","link":"https:\/\/tbekk.com\/devstream\/2024\/02\/23\/constexpr-and-consteval-functions\/","title":{"rendered":"constexpr and consteval functions"},"content":{"rendered":"\n<hr class=\"wp-block-separator has-text-color has-light-gray-color has-alpha-channel-opacity has-light-gray-background-color has-background is-style-wide\"\/>\n\n\n\n<ul class=\"wp-block-list\">\n<li><em><strong>Link:<\/strong><\/em> <a href=\"https:\/\/biowpn.github.io\/bioweapon\/2024\/02\/17\/constexpr-consteval-function.html\"><em>bioweapon<\/em><\/a><\/li>\n\n\n\n<li><em><strong>Date:<\/strong><\/em> <em>Feb 17, 2024<\/em><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-text-color has-light-gray-color has-alpha-channel-opacity has-light-gray-background-color has-background is-style-wide\"\/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"intro\">Intro<\/h2>\n\n\n\n<p>In C++23, we have three kinds of functions:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Runtime functions<\/li>\n\n\n\n<li><code>constexpr<\/code>&nbsp;functions<\/li>\n\n\n\n<li><code>consteval<\/code>&nbsp;functions<\/li>\n<\/ol>\n\n\n\n<p>They can be demonstrated as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em>\/\/ This is a pure compile-time function.<\/em>\n<em>\/\/ Any evaluation is fully done at compile-time;<\/em>\n<em>\/\/ no runtime code will be generated by the compiler, just like `static_assert`.<\/em>\n<strong>consteval<\/strong> <strong>size_t<\/strong> <strong>strlen_ct<\/strong>(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s) {\n    <strong>size_t<\/strong> n <strong>=<\/strong> 0;\n    <strong>for<\/strong> (; s&#91;n] <strong>!=<\/strong> '\\0'; <strong>++<\/strong>n);\n    <strong>return<\/strong> n;\n}\n\n<em>\/\/ This is a pure runtime function, which can only be invoked at runtime.<\/em>\n<strong>size_t<\/strong> strlen(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s);\n\n<em>\/\/ This function can be invoked both at both compile-time and at runtime,<\/em>\n<em>\/\/ depending on the context.<\/em>\n<strong>constexpr<\/strong> <strong>size_t<\/strong> strlen_dual(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s) {\n    <strong>if<\/strong> <strong>consteval<\/strong> {\n        <strong>return<\/strong> strlen_ct(s); <em>\/\/ compile-time path<\/em>\n    } <strong>else<\/strong> {\n        <strong>return<\/strong> strlen(s);    <em>\/\/ runtime path<\/em>\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Let\u2019s take it to the next level. How do the three kinds of functions interact with each other?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"who-can-call-which\">Who Can Call Which<\/h2>\n\n\n\n<p>Here \u201ccall\u201d may mean two things:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Write code to call<\/li>\n\n\n\n<li>Actual evaluation (during compile time)<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"can-constexpr-functions-call-runtime-functions\">Can&nbsp;<code>constexpr<\/code>&nbsp;functions call runtime functions?<\/h3>\n\n\n\n<p>The answer is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>You can write code to call runtime functions<\/li>\n\n\n\n<li>The code must not be reached during compile-time evaluation<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>constexpr<\/strong> <strong>int<\/strong> <strong>divide<\/strong>(<strong>int<\/strong> a, <strong>int<\/strong> b) {\n    <strong>if<\/strong> (b <strong>==<\/strong> 0) {\n        printf(\"Divide by zero\\n\");\n        <strong>return<\/strong> 0;\n    } <strong>else<\/strong> {\n        <strong>return<\/strong> a <strong>\/<\/strong> b;\n    }\n}\n\n<strong>static_assert<\/strong>( divide(6, 3) <strong>==<\/strong> 2 );  <em>\/\/ Ok<\/em>\n<strong>static_assert<\/strong>( divide(0, 0) <strong>==<\/strong> 0 );  <em>\/\/ Error: call to runtime function `printf`<\/em>\n<\/code><\/pre>\n\n\n\n<p>This is akin to the treatment of exceptions in&nbsp;<code>constexpr<\/code>&nbsp;functions; you can write&nbsp;<code>throw<\/code>, you just can\u2019t&nbsp;<code>catch<\/code>&nbsp;&#8211; any&nbsp;<code>throw<\/code>&nbsp;reached will trigger an error.<\/p>\n\n\n\n<p>C++23, specifically&nbsp;<a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2022\/p2448r2.html\">P2448<\/a>, allows you to even write:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em>\/\/ runtime function<\/em>\n<strong>void<\/strong> <strong>g<\/strong>();\n\n<em>\/\/ Ok since C++23<\/em>\n<strong>constexpr<\/strong> <strong>int<\/strong> <strong>f<\/strong>() {\n    g();\n    <strong>return<\/strong> 0;\n}\n<\/code><\/pre>\n\n\n\n<p>The above code compiles, even though any compile-time invocation of&nbsp;<code>f<\/code>&nbsp;will cause an error:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>static_assert<\/strong>( f() <strong>==<\/strong> 0 ); <em>\/\/ Error: call to runtime function `g`<\/em>\n<\/code><\/pre>\n\n\n\n<p>Ordinary invocation is fine:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>int<\/strong> n <strong>=<\/strong> f();  <em>\/\/ Ok<\/em>\n<\/code><\/pre>\n\n\n\n<p>Such functions like&nbsp;<code>f<\/code>&nbsp;are a bit deceiving: despite the&nbsp;<code>constexpr<\/code>&nbsp;specifier, they can\u2019t be evaluated at compile time.<\/p>\n\n\n\n<p>With this example in mind, a&nbsp;<code>constexpr<\/code>&nbsp;function:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>may<\/strong>&nbsp;be available for compile-time evaluation, but no guarantee that it can<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"can-consteval-functions-call-runtime-functions\">Can&nbsp;<code>consteval<\/code>&nbsp;functions call runtime functions?<\/h3>\n\n\n\n<p>At first, it may seem the answer is \u201cno\u201d.&nbsp;<code>consteval<\/code>&nbsp;functions only evaluate at compile time, so any call to runtime function must be an error, right?<\/p>\n\n\n\n<p>But C++ is actually quite permissive here: just like&nbsp;<code>constexpr<\/code>&nbsp;functions, you can write code to call runtime functions, so long as you don\u2019t actually reach that code during compile-time evaluation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em>\/\/ runtime function<\/em>\n<strong>int<\/strong> <strong>g<\/strong>();\n\n<em>\/\/ Ok, even though `f` can never be called<\/em>\n<strong>consteval<\/strong> <strong>int<\/strong> <strong>f<\/strong>() {\n    <strong>return<\/strong> g();\n}\n<\/code><\/pre>\n\n\n\n<p>So far, the only rule to remember is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>You cannot evaluate runtime functions at compile-time<\/strong><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"can-runtime-functions-call-constexpr-functions\">Can runtime functions call&nbsp;<code>constexpr<\/code>&nbsp;functions?<\/h3>\n\n\n\n<p>Mostly, yes. In fact, most of the functions in the standard library are now&nbsp;<code>constexpr<\/code>; it would be really bad if users\u2019 runtime functions can\u2019t call them!<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>constexpr<\/strong> <strong>size_t<\/strong> <strong>strlen_dual<\/strong>(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s) {\n    <strong>if<\/strong> <strong>consteval<\/strong> {\n        <strong>return<\/strong> strlen_ct(s); <em>\/\/ compile-time path<\/em>\n    } <strong>else<\/strong> {\n        <strong>return<\/strong> strlen(s);    <em>\/\/ runtime path<\/em>\n    }\n}\n\n<em>\/\/ runtime function<\/em>\n<strong>int<\/strong> main(<strong>int<\/strong> argc, <strong>char<\/strong><strong>**<\/strong> argv) {\n    <strong>int<\/strong> n1 <strong>=<\/strong> strlen_dual(argv&#91;0]);  <em>\/\/ Ok, takes the runtime path,<\/em>\n                                    <em>\/\/ because argv&#91;0] is a runtime value<\/em>\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"can-consteval-functions-call-constexpr-functions\">Can&nbsp;<code>consteval<\/code>&nbsp;functions call&nbsp;<code>constexpr<\/code>&nbsp;functions?<\/h3>\n\n\n\n<p>Yes. Intuitively:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>constexpr<\/code>&nbsp;functions support (at least advertise to) compile-time evaluation<\/li>\n\n\n\n<li><code>consteval<\/code>&nbsp;functions must evaluate at compile-time<\/li>\n<\/ul>\n\n\n\n<p>So&nbsp;<code>consteval<\/code>&nbsp;functions just take the compile-time path for the&nbsp;<code>constexpr<\/code>&nbsp;functions they call.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>constexpr<\/strong> <strong>bool<\/strong> <strong>is_constant_evaluated<\/strong>() {\n    <strong>if<\/strong> <strong>consteval<\/strong> {\n        <strong>return<\/strong> true;  <em>\/\/ compile-time path<\/em>\n    } <strong>else<\/strong> {\n        <strong>return<\/strong> false; <em>\/\/ runtime path<\/em>\n    }\n}\n\n<strong>consteval<\/strong> <strong>bool<\/strong> f() {\n    <strong>return<\/strong> is_constant_evaluated(); <em>\/\/ true<\/em>\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"can-runtime-functions-call-consteval-functions\">Can runtime functions call&nbsp;<code>consteval<\/code>&nbsp;functions?<\/h3>\n\n\n\n<p>Yes, but only if all arguments are constant expressions.<\/p>\n\n\n\n<p>The evaluation is immediate and done at compile-time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>consteval<\/strong> <strong>size_t<\/strong> <strong>strlen_ct<\/strong>(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s) {\n    ...\n}\n\n<strong>int<\/strong> main() {\n    <strong>return<\/strong> strlen_ct(\"\"); <em>\/\/ same as return 0<\/em>\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"can-constexpr-functions-call-consteval-functions\">Can&nbsp;<code>constexpr<\/code>&nbsp;functions call&nbsp;<code>consteval<\/code>&nbsp;functions?<\/h3>\n\n\n\n<p>This is the interesting part.<\/p>\n\n\n\n<p><code>consteval<\/code>&nbsp;functions can always be invoked if all the arguments are constant expressions, thus code like&nbsp;<code>strlen_ct(\"\")<\/code>&nbsp;works in&nbsp;<code>constexpr<\/code>&nbsp;functions too.<\/p>\n\n\n\n<p>There are two things that separate&nbsp;<code>constexpr<\/code>&nbsp;functions from runtime functions, both of which have to do with passing parameters down to&nbsp;<code>consteval<\/code>&nbsp;functions.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"if-consteval\"><code>if consteval<\/code><\/h4>\n\n\n\n<p>Firstly, inside the&nbsp;<code>if consteval<\/code>&nbsp;block,&nbsp;<code>constexpr<\/code>&nbsp;functions\u2019 formal parameters can be used as arguments for&nbsp;<code>consteval<\/code>&nbsp;functions.<\/p>\n\n\n\n<p>This is why our first example works:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>consteval<\/strong> <strong>size_t<\/strong> <strong>strlen_ct<\/strong>(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s) {\n    ...\n}\n\n<strong>constexpr<\/strong> <strong>size_t<\/strong> strlen_dual(<strong>const<\/strong> <strong>char<\/strong><strong>*<\/strong> s) {\n    <strong>if<\/strong> <strong>consteval<\/strong> {\n        <em>\/\/ `s` is promoted to be usable as arguments for consteval functions<\/em>\n        <strong>return<\/strong> strlen_ct(s);\n    } <strong>else<\/strong> {\n        <strong>return<\/strong> strlen(s);\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Importantly,&nbsp;<code>if consteval<\/code>&nbsp;doesn\u2019t turn function parameters into a full-fledged constant expressions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>constexpr<\/strong> <strong>int<\/strong> <strong>identity<\/strong>(<strong>int<\/strong> a) {\n    <strong>if<\/strong> <strong>consteval<\/strong> {\n        <em>\/\/ Doesn't work: error: 'a' is not a constant expression<\/em>\n        <strong>static_assert<\/strong>( a <strong>==<\/strong> a );\n        <strong>return<\/strong> a;\n    } <strong>else<\/strong> {\n        <strong>return<\/strong> a;\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>Closely related is the parameters of&nbsp;<code>consteval<\/code>&nbsp;functions:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>consteval<\/strong> <strong>int<\/strong> <strong>identity_ct<\/strong>(<strong>int<\/strong> a) {\n    <em>\/\/ Doesn't work either: error: 'a' is not a constant expression<\/em>\n    <strong>static_assert<\/strong>( a <strong>==<\/strong> a );\n    <strong>return<\/strong> a;\n}\n<\/code><\/pre>\n\n\n\n<p>I find this a bit lacking. Think about it:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>At call site, the arguments for&nbsp;<code>consteval<\/code>&nbsp;functions must be constant expressions<\/li>\n\n\n\n<li>Inside the&nbsp;<code>consteval<\/code>&nbsp;function body, the parameters are not constant expressions<\/li>\n<\/ul>\n\n\n\n<p>In short, the very same values lose their constant-expression-ness at the&nbsp;<code>consteval<\/code>&nbsp;function boundary.<\/p>\n\n\n\n<p>And apparently, I\u2019m not the only one with this thought. See&nbsp;<a href=\"https:\/\/stackoverflow.com\/questions\/56130792\/will-consteval-functions-allow-template-parameters-dependent-on-function-argumen\">this Stackoverflow question<\/a>.<\/p>\n\n\n\n<p>The example provided is:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>consteval<\/strong> <strong>auto<\/strong> <strong>foo<\/strong>(<strong>int<\/strong> i) {\n    <strong>return<\/strong> std<strong>::<\/strong>integral_constant<strong>&lt;<\/strong><strong>int<\/strong>, i<strong>&gt;<\/strong>();\n}\n<\/code><\/pre>\n\n\n\n<p>If&nbsp;<code>i<\/code>&nbsp;is allowed to be a constant expression, then&nbsp;<code>foo<\/code>&nbsp;would return different types for different values of&nbsp;<code>i<\/code>. Functions can\u2019t do that (any function has exactly one return type); only function templates can.<\/p>\n\n\n\n<p>Therefore, some language changes are needed here for this to work. Either&nbsp;<code>foo<\/code>&nbsp;is implictly turned into a function template, or we need something like&nbsp;<a href=\"https:\/\/wg21.link\/p1045r1\">P1045 &#8211; constexpr Function Parameters<\/a><\/p>\n\n\n\n<p>Anyway, what we have right now is:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Function parameters are not constant expression<\/li>\n\n\n\n<li>At best, they can be used as arguments to call&nbsp;<code>consteval<\/code>&nbsp;function inside&nbsp;<code>if consteval<\/code>&nbsp;block<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\" id=\"escalation\">Escalation<\/h4>\n\n\n\n<p>The second thing is, if a&nbsp;<code>constexpr<\/code>&nbsp;function template, outside an&nbsp;<code>if consteval<\/code>, calls a&nbsp;<code>consteval<\/code>&nbsp;function with some non-constant-expression arguments, then it becomes&nbsp;<code>consteval<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><strong>consteval<\/strong> <strong>int<\/strong> <strong>identity_ct<\/strong>(<strong>int<\/strong> x) {\n    <strong>return<\/strong> x;\n}\n\n<strong>template<\/strong> <strong>&lt;<\/strong><strong>class<\/strong> <strong>T<\/strong>&gt;\n<strong>constexpr<\/strong> T identity(T x) {\n    <em>\/\/ not in a `if consteval` block,<\/em>\n    <em>\/\/ calls consteval `identity_ct` with `x` (not a constant expression),<\/em>\n    <em>\/\/ which triggers \"escalation\",<\/em>\n    <em>\/\/ which makes `identity&lt;T&gt;` consteval<\/em>\n    <strong>return<\/strong> identity_ct(x);\n}\n\n<strong>static_assert<\/strong>( identity(0) <strong>==<\/strong> 0 ); <em>\/\/ Ok<\/em>\n\n<strong>int<\/strong> main(<strong>int<\/strong> argc, <strong>char<\/strong><strong>**<\/strong> argv) {\n    <em>\/\/ Error: `identity` can no longer be invoked ordinarily (with runtime values)<\/em>\n    <strong>return<\/strong> identity(argc);\n}\n<\/code><\/pre>\n\n\n\n<p>Note that&nbsp;<code>consteval<\/code>&nbsp;escalation works in a few other cases, but free function is not one of them:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em>\/\/ still an error; `identity` does not become consteval<\/em>\n<strong>constexpr<\/strong> <strong>int<\/strong> <strong>identity<\/strong>(<strong>int<\/strong> x) {\n    <strong>return<\/strong> identity_ct(x);\n}\n<\/code><\/pre>\n\n\n\n<p>The full mechanic and rationale is described in this paper,&nbsp;<a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2022\/p2564r3.html\">P2564 &#8211; consteval needs to propagate up<\/a>. Note that while this is a C++23 language feature, only the very recent compilers (e.g., GCC 14+, Clang 17+) support it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"some-thoughts\">Some thoughts<\/h2>\n\n\n\n<p>So we have \u201cbad\u201d&nbsp;<code>constexpr<\/code>&nbsp;functions that are runtime only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em>\/\/ runtime function<\/em>\n<strong>int<\/strong> <strong>zero<\/strong>();\n\n<strong>constexpr<\/strong> <strong>int<\/strong> <strong>f<\/strong>(<strong>int<\/strong>) {\n    <strong>return<\/strong> zero();\n}\n\n<strong>int<\/strong> main(<strong>int<\/strong> argc, <strong>char<\/strong><strong>**<\/strong>) {\n    f(argc);                  <em>\/\/ Ok<\/em>\n    <strong>static_assert<\/strong>(f(0) <strong>==<\/strong> 0); <em>\/\/ Error<\/em>\n}\n<\/code><\/pre>\n\n\n\n<p>and the other kind of \u201cbad\u201d&nbsp;<code>constexpr<\/code>&nbsp;functions that are compile-time only:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em>\/\/ compile-time function<\/em>\n<strong>consteval<\/strong> <strong>int<\/strong> <strong>zero<\/strong>() { <strong>return<\/strong> 0; }\n\n<strong>template<\/strong> <strong>&lt;<\/strong><strong>class<\/strong> <strong>T<\/strong>&gt;\n<strong>constexpr<\/strong> T f(T) {\n    <strong>return<\/strong> zero();\n}\n\n<strong>int<\/strong> main(<strong>int<\/strong> argc, <strong>char<\/strong><strong>**<\/strong>) {\n    f(argc);                  <em>\/\/ Error<\/em>\n    <strong>static_assert<\/strong>(f(0) <strong>==<\/strong> 0); <em>\/\/ Ok<\/em>\n}\n<\/code><\/pre>\n\n\n\n<p>and of course the good kind that are both runtime and compile-time friendly, which (fortuanately) all the&nbsp;<code>constexpr<\/code>&nbsp;functions in the standard library are.<\/p>\n\n\n\n<p>Perhaps it is a good practice not to write \u201cbad\u201d&nbsp;<code>constexpr<\/code>&nbsp;functions; if you mark your functions&nbsp;<code>constexpr<\/code>, make sure it is invocable at both compile-time and runtime.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Intro In C++23, we have three kinds of functions: They can be demonstrated as follows: Let\u2019s take it to the next level. How do the three kinds of functions interact&#8230; <a class=\"read-more-link\" href=\"https:\/\/tbekk.com\/devstream\/2024\/02\/23\/constexpr-and-consteval-functions\/\">Read more &raquo;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[51,220,10],"tags":[227],"class_list":["post-902","post","type-post","status-publish","format-standard","hentry","category-article","category-cpp","category-development","tag-c23"],"_links":{"self":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/902","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/comments?post=902"}],"version-history":[{"count":1,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/902\/revisions"}],"predecessor-version":[{"id":903,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/posts\/902\/revisions\/903"}],"wp:attachment":[{"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/media?parent=902"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/categories?post=902"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tbekk.com\/devstream\/wp-json\/wp\/v2\/tags?post=902"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}