Powerful debug utilities inspired by tourist’s (Gennady Korotkevich) library for printing and inspecting complex data structures during competitive programming.
Overview
Effective debugging is crucial in competitive programming. These utilities allow you to easily print and inspect:
- Basic types (int, char, string, bool)
- STL containers (vector, set, map, queue, stack)
- Pairs and tuples
- Custom data structures
- Nested containers
Basic Setup
The debug macros are enabled only during local development and disabled during online judge submission.
#ifndef ONLINE_JUDGE
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#define log(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#define log(...) 42
#endif
Key Features:
- Automatically prints variable names
- Formats output for readability
- Zero overhead in submission (compiles to nothing)
- Works with nested structures
Core Implementation
Output Stream
#define writer_out cerr
void debug_out() {
writer_out << "\n";
}
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
writer_out << to_string(H);
if(sizeof...(T)) writer_out << "] [";
else writer_out << "]";
debug_out(T...);
}
String Conversions
Basic Types
string to_string(char c) {
return "'" + string(1, c) + "'";
}
string to_string(string s) {
return '"' + s + '"';
}
string to_string(bool b) {
return (b ? "true" : "false");
}
Pairs
template <typename A, typename B>
string to_string(const pair<A, B> p) {
return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}
Tuples
template <typename A, typename B, typename C>
string to_string(const tuple<A, B, C> p) {
return "(" + to_string(get<0>(p)) + ", " +
to_string(get<1>(p)) + ", " +
to_string(get<2>(p)) + ")";
}
template <typename A, typename B, typename C, typename D>
string to_string(const tuple<A, B, C, D> p) {
return "(" + to_string(get<0>(p)) + ", " +
to_string(get<1>(p)) + ", " +
to_string(get<2>(p)) + ", " +
to_string(get<3>(p)) + ")";
}
Container Support
Vector and Lists
template <typename A>
string to_string(A v) {
bool first = true;
string res = "{";
for (auto &x : v) {
if (!first) {
res += ", ";
}
first = false;
res += to_string(x);
}
res += "}";
return res;
}
This works for: vector, set, multiset, unordered_set, list, deque
Vector of Booleans
string to_string(vector<bool> v) {
bool first = true;
string res = "{";
for (int i = 0; i < (int) v.size(); i++) {
if (!first) {
res += ", ";
}
first = false;
res += to_string(v[i]);
}
res += "}";
return res;
}
Queue
template <typename T>
string to_string(queue<T> q) {
bool first = true;
string res = "{";
while(!q.empty()) {
if (!first) {
res += ", ";
}
first = false;
res += to_string(q.front());
q.pop();
}
res += "}";
return res;
}
The queue is passed by value and consumed during printing. The original queue remains unchanged.
Stack
template <typename T>
string to_string(stack<T> st) {
bool first = true;
string res = "{";
while(!st.empty()) {
if (!first) {
res += ", ";
}
first = false;
res += to_string(st.top());
st.pop();
}
res += "}";
return res;
}
Priority Queue
template<typename T, typename Sequence=vector<T>, typename Compare=less<T>>
string to_string(priority_queue<T, Sequence, Compare> pq) {
bool first = true;
string res = "{";
while(!pq.empty()) {
if (!first) {
res += ", ";
}
first = false;
res += to_string(pq.top());
pq.pop();
}
res += "}";
return res;
}
Bitset
template <size_t N>
string to_string(const bitset<N> v) {
string res = "";
for (size_t i = 0; i < N; i++) {
res += static_cast<char>('0' + v[i]);
}
return res;
}
Usage Examples
Basic Types
int x = 42;
string name = "Alice";
char grade = 'A';
bool passed = true;
debug(x); // [x]: [42]
debug(name); // [name]: ["Alice"]
debug(grade); // [grade]: ['A']
debug(passed); // [passed]: [true]
Multiple Variables
int a = 10, b = 20, c = 30;
debug(a, b, c); // [a, b, c]: [10] [20] [30]
string s1 = "hello", s2 = "world";
debug(s1, s2); // [s1, s2]: ["hello"] ["world"]
Vectors
vector<int> v = {1, 2, 3, 4, 5};
debug(v); // [v]: [{1, 2, 3, 4, 5}]
vector<string> words = {"hello", "world"};
debug(words); // [words]: [{"hello", "world"}]
vector<bool> flags = {true, false, true};
debug(flags); // [flags]: [{true, false, true}]
Pairs and Tuples
pair<int, int> p = {3, 5};
debug(p); // [p]: [(3, 5)]
pair<string, int> entry = {"score", 100};
debug(entry); // [entry]: [("score", 100)]
tuple<int, int, int> t = {1, 2, 3};
debug(t); // [t]: [(1, 2, 3)]
Nested Structures
vector<vector<int>> matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
debug(matrix);
// [matrix]: [{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}]
vector<pair<int, int>> edges = {{1, 2}, {2, 3}, {3, 4}};
debug(edges);
// [edges]: [{(1, 2), (2, 3), (3, 4)}]
map<string, vector<int>> data = {
{"alice", {90, 85, 88}},
{"bob", {78, 82, 91}}
};
debug(data);
// [data]: [{("alice", {90, 85, 88}), ("bob", {78, 82, 91})}]
Sets and Maps
set<int> s = {3, 1, 4, 1, 5, 9};
debug(s); // [s]: [{1, 3, 4, 5, 9}]
map<int, string> m = {{1, "one"}, {2, "two"}, {3, "three"}};
debug(m);
// [m]: [{(1, "one"), (2, "two"), (3, "three")}]
multiset<int> ms = {1, 1, 2, 2, 2, 3};
debug(ms); // [ms]: [{1, 1, 2, 2, 2, 3}]
Queues and Stacks
queue<int> q;
q.push(1); q.push(2); q.push(3);
debug(q); // [q]: [{1, 2, 3}]
stack<int> st;
st.push(1); st.push(2); st.push(3);
debug(st); // [st]: [{3, 2, 1}] (top to bottom)
priority_queue<int> pq;
pq.push(3); pq.push(1); pq.push(4);
debug(pq); // [pq]: [{4, 3, 1}] (max heap)
Bitsets
bitset<8> bits("10110010");
debug(bits); // [bits]: [10110010]
bitset<16> mask = 0b1010101010101010;
debug(mask); // [mask]: [1010101010101010]
Practical Debugging Scenarios
Debugging Loops
vector<int> arr = {5, 2, 8, 1, 9};
for(int i = 0; i < arr.size(); i++) {
debug(i, arr[i]);
// [i, arr[i]]: [0] [5]
// [i, arr[i]]: [1] [2]
// [i, arr[i]]: [2] [8]
// ...
}
Debugging Algorithms
int binarySearch(vector<int>& arr, int target) {
int left = 0, right = arr.size() - 1;
while(left <= right) {
int mid = left + (right - left) / 2;
debug(left, mid, right, arr[mid]);
if(arr[mid] == target) {
return mid;
} else if(arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
Debugging Graph Algorithms
vector<vector<int>> adj = {
{1, 2},
{0, 2, 3},
{0, 1},
{1}
};
vector<bool> visited(4, false);
void dfs(int node) {
visited[node] = true;
debug(node, visited);
for(int neighbor : adj[node]) {
if(!visited[neighbor]) {
dfs(neighbor);
}
}
}
Debugging Dynamic Programming
int fibonacci(int n) {
vector<int> dp(n + 1);
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
debug(i, dp[i], dp);
}
return dp[n];
}
Advanced Techniques
Custom Data Structures
Define to_string for your custom types:
struct Point {
int x, y;
};
string to_string(Point p) {
return "Point(" + to_string(p.x) + ", " + to_string(p.y) + ")";
}
// Usage
Point p = {3, 5};
debug(p); // [p]: [Point(3, 5)]
vector<Point> points = {{1,2}, {3,4}, {5,6}};
debug(points); // [points]: [{Point(1, 2), Point(3, 4), Point(5, 6)}]
Conditional Debugging
#ifndef ONLINE_JUDGE
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#define debug_if(cond, ...) if(cond) debug(__VA_ARGS__)
#else
#define debug(...) 42
#define debug_if(cond, ...) 42
#endif
// Usage
for(int i = 0; i < 100; i++) {
debug_if(i % 10 == 0, i); // Only print every 10th iteration
}
Function Entry/Exit Tracing
#ifndef ONLINE_JUDGE
#define TRACE_FUNC debug("Entering", __FUNCTION__)
#else
#define TRACE_FUNC
#endif
int solve(int n) {
TRACE_FUNC; // ["Entering", __FUNCTION__]: ["Entering"] ["solve"]
// ... function logic
return result;
}
Complete Template
#include <bits/stdc++.h>
using namespace std;
// ============ Debug Utilities ============
string to_string(char c) {
return "'" + string(1, c) + "'";
}
string to_string(string s) {
return '"' + s + '"';
}
string to_string(bool b) {
return (b ? "true" : "false");
}
template <typename A, typename B>
string to_string(const pair<A, B> p) {
return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}
template <typename A>
string to_string(A v) {
bool first = true;
string res = "{";
for (auto &x : v) {
if (!first) res += ", ";
first = false;
res += to_string(x);
}
res += "}";
return res;
}
#define writer_out cerr
void debug_out() { writer_out << "\n"; }
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
writer_out << to_string(H);
if(sizeof...(T)) writer_out << "] [";
else writer_out << "]";
debug_out(T...);
}
#ifndef ONLINE_JUDGE
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif
// =========================================
int main() {
// Your code here
vector<int> v = {1, 2, 3, 4, 5};
debug(v);
return 0;
}
Best Practices
Always use debug macros: They’re automatically disabled during submission, so use them liberally during development.
Debug at key points: Add debug statements before and after critical operations to track state changes.
Use meaningful variable names: The macro prints variable names, so descriptive names make debugging easier.
Don’t debug inside tight loops: Printing too much output can slow down execution significantly. Use conditional debugging instead.
Remember to remove excessive debug statements: While they’re disabled in submission, too many can make local testing slow.
Alternative Debug Approaches
Using DEBUG Flag
#ifdef DEBUG
#define debug(...) writer_out << "[" << #__VA_ARGS__ << "]: [" , debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif
// Compile with: g++ -DDEBUG solution.cpp
#ifdef DEBUG
#include "debug.h"
#else
#define debug(...) 42
#endif
Create debug.h with all debug utilities, and only include it during local development.