Skip to content
Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Why is a raven like a writing desk?

Thoughts both confusing and enlightening.

Formatted Diagnostics with C++20

elbeno, 10 December, 202410 December, 2024

C++26 adds formatted diagnostics with static_assert, something like this:

static_assert(
  sizeof(int) == 4,
  std::format("Expected 4, got {}", 
              sizeof(int)));

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 – we can get the compiler to output our formatted text.

We’re going to use a combination of techniques and serendipities to get there. And yes, macros are involved. Such is life. Here we go.

First: of course we can do string formatting in a constexpr context. I’m using fmtlib for this.

Second: we’ll use a C++20 structural-string type for a template argument. Most implementations are pretty much the same.

template <std::size_t N>
struct cx_string {
  // some constructors etc...

  std::array<char, N> value{};
};

template <cx_string S>
auto f() {
  // S is a compile-time string, yay
}

Third: in order to preserve the constexpr nature of our format function arguments (which are naturally known at compile time), we can use the wrap-in-a-lambda trick.

auto f(auto l) {
  // f is not necessarily marked constexpr
  // l cannot be marked constexpr
  // but we can get a constexpr value out
  constexpr auto value = l();
}

// lambda's call operator is constexpr
// and 42 is a compile-time value
f([] { return 42; });

Fourth: it is often convenient to print type and enumeration names at compile time, so we can use the well-known __PRETTY_FUNCTION__ trick to turn them into string_views at compile time.

Fifth: it’s going to be convenient to treat types and values the same, so we’ll use the previously-known trick for that, and we’ll combine it with the constexpr-preserving wrap-in-a-lambda trick.

// treat all of these the same, for formatting
CX_VALUE(42);
CX_VALUE(int);
CX_VALUE("Hello world"sv);

Sixth: we’ll use the happy fact that compiler diagnostics print compile-time strings out for us.

template <cx_string>
struct undef;

undef<"Hello, world!"> q{};
error: implicit instantiation of undefined template 'undef<{{"Hello, world!"}}>'

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:

undef<"Hello, world! this is a string that is longer than the compiler likes to print"> q{};
error: implicit instantiation of undefined template 'diag<cx_string<79>{{"hello, world! this is a string tha[...]"}}>'

Clang elides the string in the diagnostic after a certain size. It even does this when we pass -fno-elide-type (which is arguably a bug). So we’ll have to find another trick.

Seventh: somewhere in recent history, concepts were billed as an improvement to diagnostics. Which means compilers like to output concept check failures verbosely.

template <cx_string S>
concept check = false;

static_assert(check<"Hello, world! this is a string that is long">);
error: static assertion failed
...
note: because cx_string<44>{{"Hello, world! this is a string that is long"}} does not satisfy 'check'

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.)

We’re done. All that remains is to put all these things together with about as reasonable an interface as we can manage, in C++20.

template <typename T> constexpr auto f() {
    STATIC_ASSERT(
      std::is_unsigned_v<T>,
      "hello {} {} {}", 
      CX_VALUE("world"), CX_VALUE(T), 123);
}

auto main() -> int { f<int>(); }
...
note: because 'check<cx_string<20>{{"hello world int 123"}}>' evaluated to false

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.

C++

Post navigation

Previous post
Next post

Related Posts

How to print anything in C++ (part 4)

2 February, 201530 June, 2015

Part 1 Part 2 Part 3 Part 4 Part 5 Postscript Callable things. There are several types: functions member functions std::functions bind expressions lambdas objects that support operator() (function objects) So, going back to my tag code, so far (with everything I’ve added) and including callable things, it will look…

Read More

CppCon 2017 Trip Report

30 September, 201730 September, 2017

Last week in Bellevue, WA, around 1100 C++ programmers got together for CppCon. I love this conference – it’s a chance to meet up with my existing C++ community friends and make new ones, to learn new techniques and explore parts of C++, and to get excited about where C++…

Read More

Floating-point maths, constexpr style

13 October, 201515 October, 2015

(Start at the beginning of the series – and all the source can be found in my github repo) To ease into constexpr programming I decided to tackle some floating-point maths functions. Disclaimer: I’m not a mathematician and this code has not been rigorously tested for numeric stability or convergence…

Read More
©2026 Why is a raven like a writing desk? | WordPress Theme by SuperbThemes