Last active
September 8, 2025 02:03
-
-
Save dk949/392c55c65e3c4efb89c7b89bd4390ba4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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