Hi Codeforces!
A mod bug in my combination code recently cost me two wrong submissions on a Div. 3 problem C. Stripped down, it was this:
const int MOD = 1e18 + 7; // "big enough that I never need a real mod"
...
fact[i] = fact[i - 1] * i % MOD;
fact[i - 1] * i overflows long long long before the % MOD runs, and the program happily printed garbage. Here is the same code in my current setup, first run, sample input:
✗ Runtime error: UB trap, one of
• signed overflow: a * b or a + b too big for long long
• 1 << j with j >= 31: write 1LL << j
• x / 0 or x % 0
• C-array index out of range
→ line 12: fact[i] = fact[i - 1] * i % MOD;
This post is about the pieces behind that: a debug header that prints anything you give it, and a build that catches the mistakes the judge only reports as "Wrong answer on test 2". Everything is on GitHub: harly24-cp. It works with any editor; I use Sublime, and the repo includes the build system for it.
Part 1: debug.h
The idea is tourist's debug_out: a macro that prints names next to values. Mine went through five versions in two years, and each one died in some contest where I needed to print something it couldn't. This is the version that finally prints everything I throw at it:
debug(*min_element(diff.begin() + 2, diff.end()), adj, ms); // adj: map<string, vector<pair<int, int>>>
debug(dp); // vector<vector<long long>>
debug();
[main:14] *min_element(diff.begin() + 2, diff.end()) = 1 | adj = {"a": [(1, 2)], "b": []} | ms = {2, 2, 5}
[main:15] dp =
0: [0, inf, inf, inf]
1: [inf, inf, 5, inf]
[main:16] reached
What it handles that the templates I used before (and most I found here) don't:
- any nesting, like
map<pair<int, int>, vector<set<int>>> (overload-based templates only see the overloads declared above them, so nested types often don't compile) - custom comparators and hashes:
set<int, greater<int>>, an unordered_map with your own hash multiset duplicates (some templates print it through a set) - arguments with commas, like the
*min_element(...) call above, which breaks the usual "split the names at commas" macro (the comments of this blog ran into it) __int128, C arrays, stack, queue, priority_queue (printed without popping anything) and pb_ds ordered_set - DP tables: 2D containers print one row per line with the row index, and your
inf prints as inf - crashes: it writes to
debug.txt and flushes every line, so a segfault doesn't eat the output, and debug() with no arguments prints reached, so the last one in the file is the last line that ran - endless loops: a
debug() inside one used to write gigabytes and freeze my editor; after 10,000 lines it now ends the program
Two rules I learned the hard way. First, keep it in a header, never in the submission, and compile locally with -DLOCAL:
#ifdef LOCAL
#include "debug.h"
#else
#define debug(...)
#endif
Second, don't put #define int long long (or any of your template's macros) in the debug header. My old one did, so int was 64-bit locally and 32-bit on the judge, which hides exactly the overflow bugs you want to find.
How it works: a single print template that dispatches with if constexpr on C++20 concepts (string-like, range, map, container adaptor, tuple-like, streamable). It calls itself for nested types, so there is no declaration order to get wrong. Names are split at top-level commas only, with brackets and string literals tracked.
debug.h (215 lines, C++20)// debug.h: local-only debug printer. Included through AllHeaders.h when built with -DLOCAL.
//
// debug(x, v, mp); [Harly24:42] x = 5 | v = [1, 2, 3] | mp = {1: [2, 3]}
// debug(); [Harly24:42] reached (crash hunting: the last line printed is the last line reached)
// debug("after dfs"); [Harly24:42] after dfs (a string literal prints as a label)
// debug(dp); grids (vector<vector<..>>, vector<string>, adjacency lists) print one row per line
//
// Prints any nesting of numbers, __int128, char, bool, strings, pair, tuple, every STL container
// (custom comparators and hashes too), C arrays, stack/queue/priority_queue, pb_ds ordered_set,
// and any type with operator<<. Writes to debug.txt and flushes every line, so output survives a crash.
// If debug() floods (over 10000 lines or 4 MB: an endless loop), the run ends with exit code 86.
//
// Self-contained on purpose: it is included before the template's macros (#define int long long, ...)
// so local types match the judge's.
#pragma once
#include <bits/stdc++.h>
namespace dbg {
// ---- settings ----
inline constexpr std::size_t max_items = 100; // per container; the rest prints as "...+N more"
inline constexpr std::size_t max_lines = 10000; // per run; keeps debug.txt small enough for Sublime
inline constexpr std::streamoff max_bytes = 4 << 20; // 4 MB, for runs with few but huge lines
inline constexpr bool stop_on_flood = true; // past either cap, end the program (exit code 86): it's a runaway loop
inline constexpr long long shown_as_inf = 2e18; // the template's inf, printed as "inf"
inline std::ofstream file = [] { std::ofstream f("debug.txt"); f.precision(10); return f; }();
inline std::ostream& out() { return file.is_open() ? static_cast<std::ostream&>(file) : std::cerr; }
inline std::size_t lines_written = 0;
inline bool stopped = false;
inline bool flooded() {
if (stopped) return true;
if (lines_written < max_lines && !(file.is_open() && file.tellp() > max_bytes)) return false;
out() << "[debug] flood: over " << max_lines << " lines or " << (max_bytes >> 20)
<< " MB. Endless loop? (caps are in debug.h)" << std::endl;
stopped = true;
if (stop_on_flood) { std::cout.flush(); std::_Exit(86); }
return true;
}
// ---- type tests ----
template <class T> using bare = std::remove_cvref_t<T>;
template <class> inline constexpr bool always_false = false;
template <class T> concept StringLike = std::is_convertible_v<const T&, std::string_view>;
template <class T> concept Range = requires(const T& x) { std::begin(x); std::end(x); };
template <class T> concept Adaptor = requires { typename T::container_type; } && !Range<T>;
template <class T> concept TupleLike = requires { std::tuple_size<T>::value; } && !Range<T>;
template <class T> concept Streamable = requires(std::ostream& o, const T& x) { o << x; };
template <class T> concept SetOrMap = requires { typename T::key_type; };
template <class T> concept MapLike = requires { typename T::mapped_type; };
template <class T> concept IsPair = requires(const T& p) { p.first; p.second; };
template <class T> using element_t = bare<decltype(*std::begin(std::declval<const T&>()))>;
template <class T> concept Grid = Range<T> && !StringLike<T> && Range<element_t<T>>;
// stack/queue/priority_queue keep their container in a protected member `c`.
template <class A>
const typename A::container_type& underlying(const A& a) {
struct Peek : A {
static const typename A::container_type& get(const A& x) { return x.*&Peek::c; }
};
return Peek::get(a);
}
inline std::string i128_to_string(unsigned __int128 x, bool negative) {
std::string s;
do { s += char('0' + int(x % 10)); x /= 10; } while (x);
if (negative) s += '-';
return {s.rbegin(), s.rend()};
}
// ---- printer ----
template <class T> void print(std::ostream& o, const T& x);
template <class R> void print_items(std::ostream& o, const R& r, const char* open, const char* close) {
o << open;
std::size_t shown = 0, total = 0;
for (const auto& item : r) {
if (shown < max_items) {
if (shown++) o << ", ";
if constexpr (MapLike<R> && IsPair<bare<decltype(item)>>) {
print(o, item.first); o << ": "; print(o, item.second);
} else {
print(o, item);
}
}
++total;
}
if (total > shown) o << ", ...+" << total - shown << " more";
o << close;
}
template <class T> void print(std::ostream& o, const T& x) {
if constexpr (std::is_same_v<T, char>) {
o << '\'' << x << '\'';
} else if constexpr (std::is_same_v<T, bool>) {
o << (x ? "true" : "false");
} else if constexpr (StringLike<T>) {
if constexpr (std::is_pointer_v<T>) { if (!x) { o << "nullptr"; return; } }
o << '"' << std::string_view(x) << '"';
} else if constexpr (std::is_same_v<T, __int128>) {
o << i128_to_string(x < 0 ? -(unsigned __int128)x : (unsigned __int128)x, x < 0);
} else if constexpr (std::is_same_v<T, unsigned __int128>) {
o << i128_to_string(x, false);
} else if constexpr (std::is_integral_v<T>) {
if constexpr (std::is_signed_v<T>) {
if (x == std::numeric_limits<T>::max() || (long long)x == shown_as_inf) { o << "inf"; return; }
if (x == std::numeric_limits<T>::min() || (long long)x == -shown_as_inf) { o << "-inf"; return; }
}
o << +x;
} else if constexpr (std::is_floating_point_v<T>) {
o << x;
} else if constexpr (Range<T>) {
if constexpr (SetOrMap<T>) print_items(o, x, "{", "}");
else print_items(o, x, "[", "]");
} else if constexpr (Adaptor<T>) {
if constexpr (requires { typename T::value_compare; }) {
T copy = x; // priority_queue: pop order, top first
std::vector<typename T::value_type> order;
while (!copy.empty()) { order.push_back(copy.top()); copy.pop(); }
print_items(o, order, "[", "]");
} else {
print_items(o, underlying(x), "[", "]"); // stack: bottom..top, queue: front..back
}
} else if constexpr (TupleLike<T>) {
o << '(';
std::apply([&](const auto&... e) { std::size_t i = 0; ((o << (i++ ? ", " : ""), print(o, e)), ...); }, x);
o << ')';
} else if constexpr (Streamable<T>) {
o << x;
} else {
static_assert(always_false<T>, "debug(): no printer for this type. Give it `friend ostream& operator<<(ostream&, const T&)` or debug its fields.");
}
}
template <class T> void print_value(std::ostream& o, const T& x) {
if constexpr (Grid<T>) {
std::size_t rows = 0;
for (const auto& r : x) {
if (rows < max_items) {
o << "\n " << std::setw(3) << rows << ": ";
print(o, r);
++lines_written;
}
++rows;
}
if (rows == 0) o << "[]";
if (rows > max_items) o << "\n ...+" << rows - max_items << " more rows";
} else {
print(o, x);
}
}
// "a, f(b, c), mp[{1, 2}]" -> {"a", "f(b, c)", "mp[{1, 2}]"}: splits at top-level commas only.
inline std::vector<std::string> split_names(std::string_view s) {
std::vector<std::string> names(1);
int depth = 0;
char quote = 0;
for (std::size_t i = 0; i < s.size(); ++i) {
char c = s[i];
if (quote) {
if (c == '\\' && i + 1 < s.size()) { names.back() += c; c = s[++i]; }
else if (c == quote) quote = 0;
} else if (c == '"' || c == '\'') {
quote = c;
} else if (c == '(' || c == '[' || c == '{') {
++depth;
} else if (c == ')' || c == ']' || c == '}') {
--depth;
} else if (c == ',' && depth == 0) {
names.emplace_back();
continue;
}
names.back() += c;
}
for (auto& n : names) {
auto first = n.find_first_not_of(' '), last = n.find_last_not_of(' ');
n = first == std::string::npos ? "" : n.substr(first, last - first + 1);
}
return names;
}
template <class... Ts>
void emit(const char* func, int line, const char* names, const Ts&... values) {
if (flooded()) return;
std::ostream& o = out();
o << '[' << func << ':' << line << "] ";
if constexpr (sizeof...(Ts) == 0) {
o << "reached";
} else {
const std::vector<std::string> parts = split_names(names);
const bool named = parts.size() == sizeof...(Ts); // false only when a comma sits inside <...>
if (!named) o << names << " = ";
std::size_t i = 0;
auto one = [&](const auto& v) {
if (i) o << " | ";
if constexpr (StringLike<bare<decltype(v)>>) {
if (named && parts[i].starts_with('"')) { o << std::string_view(v); ++i; return; }
}
if (named) o << parts[i] << (Grid<bare<decltype(v)>> ? " =" : " = ");
print_value(o, v);
++i;
};
(one(values), ...);
}
o << '\n';
o.flush();
++lines_written;
}
} // namespace dbg
#define debug(...) ::dbg::emit(__func__, __LINE__, #__VA_ARGS__ __VA_OPT__(,) __VA_ARGS__)
Your own struct only needs an operator<<:
struct Edge { int u, v, w; friend ostream& operator<<(ostream& o, const Edge& e) { return o << e.u << "-" << e.v << " (" << e.w << ")"; } };
Part 2: the build
andreyv's Catching silly mistakes with GCC is still the best list of debug flags. This is the set I run on every Ctrl+B; it's fast enough to never turn off:
g++ -std=c++20 -O1 -g1 -DLOCAL -D_GLIBCXX_DEBUG \
-fsanitize=signed-integer-overflow,shift,integer-divide-by-zero,bounds -fsanitize-trap=all \
-Wall -Wextra -Wshadow=local -Werror=return-type
-fsanitize-trap=all (on GCC 13 and older: -fsanitize-undefined-trap-on-error) needs no runtime library, so it also works with Homebrew GCC on macOS, where -fsanitize=undefined doesn't even link. It stops at the faulting instruction without a message, so my script reruns the program once under lldb or gdb and prints the line, as in the example at the top.
A few things I only found out while building this:
x / 0 doesn't crash on Apple Silicon: ARM's divide instruction returns 0, so the code passes locally and gets a runtime error on the judge. The trap above catches it. - The default stack on macOS is 8 MB and Codeforces gives 256 MB, so a DFS that goes 10^6 deep segfaults locally and passes on the judge. On macOS link with
-Wl,-stack_size,0x20000000; on Linux use ulimit -s. - On macOS,
g++ is Apple clang: no pb_ds, and not what the judge runs. Use Homebrew's g++-15. - GCC 16's
<bits/stdc++.h> no longer declares assert. If you use assert, add #include <cassert> before your compiler, or the judge's, upgrades. -D_GLIBCXX_DEBUG catches a sort comparator that uses <= ("comparison doesn't meet irreflexive requirements"), a classic runtime error on the judge.
The script (cprun) wraps all of this with three guards I added after an endless loop froze my Mac: a time limit, a memory watchdog (macOS ignores ulimit -v, and push_back in an endless loop can swap the whole machine), and a 10 MB cap on output files. Every run ends with one verdict and the CPU time, which is what the judge measures.
Part 3: a generator for max tests
Samples never tell you about TLE. gen.h writes the common Codeforces input formats in one call, respecting "sum of n over all tests":
arrays(10, 200000, 1, 1e9); // t = 10 tests, sum of n = 2e5
trees(1, 200000, "line"); // one long path, for deep DFS
update_queries(0, 200000, 200000, 1, 1e9); // no t line: n q / array / queries
There are 20 formats, plus the building blocks for anything else.
That's it
Repo: github.com/Abhayanthk/harly24-cp. It runs on macOS and Linux (WSL on Windows), with tests on both in CI. If debug() can't print some type you use, post it in the comments and I'll add it.
Thanks to tourist for the debug_out idea, and to andreyv for the flags blog that started all of this for me.