Skip to content

Instantly share code, notes, and snippets.

@dk949
Last active September 8, 2025 02:03
Show Gist options
  • Save dk949/392c55c65e3c4efb89c7b89bd4390ba4 to your computer and use it in GitHub Desktop.
Save dk949/392c55c65e3c4efb89c7b89bd4390ba4 to your computer and use it in GitHub Desktop.
#ifndef UT_TRACED_ERROR_HPP
#define UT_TRACED_ERROR_HPP
/**
* An exception type which automatically stores a standard stacktrace
*
* Usage:
* On gcc and clang, linking against libstdc++exp (-lstdc++exp) is required and compiling with -g -Og and
* linking with -lg is advised.
*
* `throw`ing an instance of `TracedError` (or types derived from it) will capture the stack trace.
*
* It can be retrieved with the `trace()` member function.
*
* The exception can be formatted with `std::format` and friends, if the `?` formatter is used, the stack is
* printed.
*
* When creating new types through inheritance, it may be preferable to call the constructor taking an offset
* as its first argument, incrementing it for each class in the inheritance chain.
*
* Defining the UT_TRACED_ERROR_DISABLE macro disables everything to do with `std::stacktrace`. This includes
* the `trace` member function.
*/
#include <concepts>
#include <utility>
#if __cplusplus < 202'302L
# error this file has to be compiled with at least C++23
#endif
#include <format>
#include <stdexcept>
#ifndef UT_TRACED_ERROR_DISABLE
# include <stacktrace>
#endif // !UT_TRACED_ERROR_DISABLE
namespace ut {
class TracedError : public std::runtime_error {
#ifndef UT_TRACED_ERROR_DISABLE
std::stacktrace m_trace;
#endif // !UT_TRACED_ERROR_DISABLE
public:
template<typename... Args>
TracedError(std::stacktrace::size_type offset, std::format_string<Args...> fmt, Args &&...args)
: std::runtime_error(std::format(fmt, std::forward<Args>(args)...))
#ifndef UT_TRACED_ERROR_DISABLE
, m_trace(std::stacktrace::current(offset + 1))
#endif // !UT_TRACED_ERROR_DISABLE
{
}
template<typename... Args>
TracedError(std::format_string<Args...> fmt, Args &&...args)
: TracedError(1, fmt, std::forward<Args>(args)...) { }
#ifndef UT_TRACED_ERROR_DISABLE
[[nodiscard]]
std::stacktrace const &trace() const {
return m_trace;
}
#endif // !UT_TRACED_ERROR_DISABLE
std::string traceStr() const {
#ifndef UT_TRACED_ERROR_DISABLE
return std::to_string(trace());
#else
return {};
#endif // !UT_TRACED_ERROR_DISABLE
}
};
} // namespace ut
template<std::derived_from<::ut::TracedError> T>
struct std::formatter<T> {
private:
bool print_trace = false;
void format_string_should_contain_question_mark_or_be_empty() { }
public:
constexpr auto parse(std::format_parse_context &ctx) {
auto begin = ctx.begin();
#ifndef UT_TRACED_ERROR_DISABLE
for (; begin != ctx.end() && *begin != '}'; ++begin) {
switch (*begin) {
case '?': print_trace = true; break;
default: format_string_should_contain_question_mark_or_be_empty();
}
}
#endif // !UT_TRACED_ERROR_DISABLE
return begin;
}
auto format(T const &s, std::format_context &ctx) const {
auto out = std::format_to(ctx.out(), "{}", s.what());
#ifndef UT_TRACED_ERROR_DISABLE
if (print_trace) out = std::format_to(out, "\n{}", s.trace());
#endif // !UT_TRACED_ERROR_DISABLE
return out;
}
};
#endif // UT_TRACED_ERROR_HPP
an exception type that automatically stores a C++23 standard stacktrace
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment