Panic better using modern C++

Crashing can be an effective way to handle errors.

When deployed correctly, crashing reduces complexity by eliminating unnecessary code pathways and simplifies debugging by producing a more succinct error log. For more information on how to crash correctly, I recommend reading Matt Klein’s excellent blog post Crash early and crash often for more reliable software.

Given how useful crashing is, it should be easy and convenient to terminate a program with a helpful error message. Yet, C++ projects frequently resort to cumbersome techniques such as macros or building error messages with string streams. Fortunately, with modern C++, we can do better.

In this article, I will show how to build a panic function that

  1. prints the source location of the call site

panic("die");  // outputs <file>:<line> panic: die
  1. allows for format strings with arguments

panic("{} {}", "abc", 123);  // outputs <file>:<line> panic: abc 123
  1. and does full type checking of the format string

panic("{z9}", 123);  // compilation error

To build panic, we will use two components from C++20: std::source_location and std::format.

std::source_location allows us to capture the file and line of the call site:

void f(std::source_location loc = std::source_location::current()) {
  // ...
}
// ...
f(); // loc will have this location's file and line number

And std::format gives us a type-safe extensible replacement for printf style formatting.

Since panic takes a variable number of arguments, we can’t capture the source location using a default parameter of the function. Instead, we’ll wrap the format argument.

template <class... Args>
struct panic_format {
  template <class T>
  consteval panic_format(    // note: consteval is what allows for compile-time checking of the
      const T &s,            //       format string
      std::source_location loc = std::source_location::current()) noexcept
      : fmt{s}, loc{loc} {}

  std::format_string<Args...> fmt;
  std::source_location loc;
};

Now, we can write our panic function like this

template <class... Args>
[[noreturn]] void panic(
  panic_format<std::type_identity_t<Args>...> fmt, // std::type_identity_t is needed to prevent
  Args &&...args) noexcept                         // type deduction of the format string's
{                                                  // arguments.
  auto msg = std::format("{}:{} panic: {}\n", fmt.loc.file_name(), fmt.loc.line(),
                         std::format(fmt.fmt, std::forward<Args>(args)...));
  panic_impl(msg.c_str()); // print msg and abort
}

Full source code is available here and an example of usage can be found here.

Stay up to date