{"id":1783,"date":"2024-12-10T08:35:35","date_gmt":"2024-12-10T15:35:35","guid":{"rendered":"https:\/\/www.elbeno.com\/blog\/?p=1783"},"modified":"2024-12-10T09:38:33","modified_gmt":"2024-12-10T16:38:33","slug":"formatted-diagnostics-with-c20","status":"publish","type":"post","link":"https:\/\/www.elbeno.com\/blog\/?p=1783","title":{"rendered":"Formatted Diagnostics with C++20"},"content":{"rendered":"\n<p>C++26 adds formatted diagnostics with <code>static_assert<\/code>, something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>static_assert(\n  sizeof(int) == 4,\n  std::format(\"Expected 4, got {}\", \n              sizeof(int)));<\/code><\/pre>\n\n\n\n<p>The benefit of course is that when such an assertion fails, the compiler outputs a diagnostic that we control. But can this be done in C++20? Well, this is C++! So the answer is a qualified yes &#8211; we <em>can<\/em> get the compiler to output our formatted text.<\/p>\n\n\n\n<p>We&#8217;re going to use a combination of techniques and serendipities to get there. And yes, macros are involved. Such is life. Here we go.<\/p>\n\n\n\n<p><strong>First<\/strong>: of course we can do string formatting in a <code>constexpr<\/code> context. I&#8217;m using <a href=\"https:\/\/github.com\/fmtlib\/fmt\">fmtlib<\/a> for this.<\/p>\n\n\n\n<p><strong>Second<\/strong>: we&#8217;ll use a C++20 structural-string type for a template argument. Most implementations are pretty much the same.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;std::size_t N&gt;\nstruct cx_string {\n  \/\/ some constructors etc...\n\n  std::array&lt;char, N&gt; value{};\n};\n\ntemplate &lt;cx_string S&gt;\nauto f() {\n  \/\/ S is a compile-time string, yay\n}<\/code><\/pre>\n\n\n\n<p><strong>Third<\/strong>: in order to preserve the <code>constexpr<\/code> nature of our format function arguments (which are naturally known at compile time), we can use the wrap-in-a-lambda trick.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>auto f(auto l) {\n  \/\/ f is not necessarily marked constexpr\n  \/\/ l cannot be marked constexpr\n  \/\/ but we can get a constexpr value out\n  constexpr auto value = l();\n}\n\n\/\/ lambda's call operator is constexpr\n\/\/ and 42 is a compile-time value\nf(&#91;] { return 42; });<\/code><\/pre>\n\n\n\n<p><strong>Fourth<\/strong>: it is often convenient to print type and enumeration names at compile time, so we can use the well-known <code>__PRETTY_FUNCTION__<\/code> trick to turn them into <code>string_view<\/code>s at compile time.<\/p>\n\n\n\n<p><strong>Fifth<\/strong>: it&#8217;s going to be convenient to treat types and values the same, so we&#8217;ll use the <a href=\"https:\/\/www.elbeno.com\/blog\/?p=1771\" data-type=\"link\" data-id=\"https:\/\/www.elbeno.com\/blog\/?p=1771\">previously-known<\/a> trick for that, and we&#8217;ll combine it with the <code>constexpr<\/code>-preserving wrap-in-a-lambda trick.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ treat all of these the same, for formatting\nCX_VALUE(42);\nCX_VALUE(int);\nCX_VALUE(\"Hello world\"sv);<\/code><\/pre>\n\n\n\n<p><strong>Sixth<\/strong>: we&#8217;ll use the happy fact that compiler diagnostics print compile-time strings out for us.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;cx_string&gt;\nstruct undef;\n\nundef&lt;\"Hello, world!\"&gt; q{};<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>error: implicit instantiation of undefined template 'undef&lt;{{\"Hello, world!\"}}&gt;'<\/code><\/pre>\n\n\n\n<p>This works since clang 15 and GCC 13.2 AFAIK, but this is where we leave MSVC, which is still printing ASCII codes, behind. Also, clang has another problem:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>undef&lt;\"Hello, world! this is a string that is longer than the compiler likes to print\"&gt; q{};<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>error: implicit instantiation of undefined template 'diag&lt;cx_string&lt;79&gt;{{\"hello, world! this is a string tha&#91;...]\"}}&gt;'<\/code><\/pre>\n\n\n\n<p>Clang elides the string in the diagnostic after a certain size. It even does this when we pass <code><a href=\"https:\/\/clang.llvm.org\/docs\/ClangCommandLineReference.html#cmdoption-clang-fno-elide-type\" data-type=\"link\" data-id=\"https:\/\/clang.llvm.org\/docs\/ClangCommandLineReference.html#cmdoption-clang-fno-elide-type\">-fno-elide-type<\/a><\/code> (which is arguably a bug). So we&#8217;ll have to find another trick.<\/p>\n\n\n\n<p><strong>Seventh<\/strong>: somewhere in recent history, concepts were billed as an improvement to diagnostics. Which means compilers like to output concept check failures verbosely.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;cx_string S&gt;\nconcept check = false;\n\nstatic_assert(check&lt;\"Hello, world! this is a string that is long\"&gt;);<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>error: static assertion failed\n...\nnote: because cx_string&lt;44>{{\"Hello, world! this is a string that is long\"}} does not satisfy 'check'<\/code><\/pre>\n\n\n\n<p>So when we use a concept check failure, we get the whole message. (Thanks to Patrick Roberts for making me aware of this last piece of the puzzle.)<\/p>\n\n\n\n<p><strong>We&#8217;re done<\/strong>. All that remains is to put all these things together with about as reasonable an interface as we can manage, in C++20.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>template &lt;typename T&gt; constexpr auto f() {\n    STATIC_ASSERT(\n      std::is_unsigned_v&lt;T&gt;,\n      \"hello {} {} {}\", \n      CX_VALUE(\"world\"), CX_VALUE(T), 123);\n}\n\nauto main() -&gt; int { f&lt;int&gt;(); }<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>...\nnote: because 'check&lt;cx_string&lt;20&gt;{{\"hello world int 123\"}}&gt;' evaluated to false<\/code><\/pre>\n\n\n\n<p>There you have it: with a couple of the major compilers at least, we have user-formatted text in compiler diagnostics with C++20. This is C++. Putting a bunch of tricks under the hood in order to have a nice interface experience is what we do.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>C++26 adds formatted diagnostics with static_assert, something like this: The benefit of course is that when such an assertion fails, the compiler outputs a diagnostic that we control. But can this be done in C++20? Well, this is C++! So the answer is a qualified yes &#8211; we can get&#8230;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[22],"tags":[],"class_list":["post-1783","post","type-post","status-publish","format-standard","hentry","category-cpp"],"_links":{"self":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1783","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1783"}],"version-history":[{"count":4,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1783\/revisions"}],"predecessor-version":[{"id":1787,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/1783\/revisions\/1787"}],"wp:attachment":[{"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1783"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1783"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.elbeno.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1783"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}