Header-only library for debugging. Allows to output the contents of any struct, class, or primitive. Provides functions to dump names and values of private and public fields, items in STL containers and pairs in maps into wide strings.
Tested on Windows 10 22h2 with Clang 22.1.4 (Msys64, x86_64-w64-windows-gnu). Requires Clang.
Copy dump.hpp to your include directory:
cp dump.hpp /path/to/your/include/And #include it in your project:
#include "dump.hpp"
Or use CMake to integrate as a package:
find_package(dump-struct REQUIRED)
target_link_libraries(your_target dump-struct::dump-struct)And build your project with inlcuded library:
cd /path/to/your/project/ && cmake --build cmake-build-debug --config Debug 2>&1
If you're using CLion, you need to add path to dump.hpp in your CMakeLists.txt and create new build target using
add_executable. For example, if you downloaded dump.hpp to Lib directory, specify it in the include_directories:
include_directories("Lib")
add_executable(main main.cpp)Now reload cmake project and add this line to main.cpp:
#include "dump.hpp"Select main target at the top and try to build it.

Now you should be able to use anything from dbg namespace.
You also need to install CLang toolchain (MSYS2 on Windows). It is required for the library core. CLang is awesome compiler for beginners because it displays readable compilation errors.

Full CMakeLists
This is my CMakeLists for one of my C++ projects:
cmake_minimum_required(VERSION 3.28)
project(CPP)
set(CMAKE_CXX_STANDARD 23)
# My libraries
include_directories(
"C:/Cfg/CPP/icecream-cpp"
"C:/Cfg/CPP/generate-random"
"Lib"
)
# Auto-generate CMake targets from .cpp files
file(GLOB SOURCES
"src/*.cpp"
"main.cpp")
set(REL_SOURCES)
foreach(source IN LISTS SOURCES)
file(RELATIVE_PATH rel_source "${CMAKE_CURRENT_SOURCE_DIR}" "${source}")
if(rel_source MATCHES "^\\.")
continue()
endif()
list(APPEND REL_SOURCES ${rel_source})
endforeach()
set(SOURCES ${REL_SOURCES})
#message("SOURCES after filter: ${SOURCES}")
foreach(source_file IN LISTS SOURCES)
get_filename_component(file_name ${source_file} NAME_WE)
get_filename_component(dir ${source_file} DIRECTORY)
if(NOT dir STREQUAL "")
string(REPLACE " " "_" dir ${dir})
string(REPLACE "/" "_" dir ${dir})
set(target_name ${dir}_${file_name})
else()
set(target_name ${file_name})
endif()
add_executable(${target_name} ${source_file})
target_compile_options(${target_name} PRIVATE -Wno-system-headers)
endforeach()#include "dump.hpp"
#include <vector>
#include <string>
struct Point {
int x = 10;
int y = 20;
};
int main() {
Point p;
log(p); // Point = { x = 10, y = 20 }
std::vector<int> vec {1, 2, 3};
log(vec); // vec = [ 1, 2, 3 ]
std::string str = "hello";
log(str); // str = "hello"
return 0;
}Outputs variable name and its value to the console using wprintf.
Parameters:
obj– Any object, struct, primitive, string, or container
Output Example:
int value = 42;
log(value); // value = 42
std::string str = "hello";
log(str); // str = "hello"
std::vector<int> vec {1, 2, 3};
log(vec); // vec = [ 1, 2, 3 ]Note
The macro respects the dbg::verbose flag. Set it to false to disable all logging:
dbg::verbose = false;Dumps any object into a std::wstring. Supports objects, strings and primitives.
// For classes and structs (uses __builtin_dump_struct intrinsic)
// https://clang.llvm.org/docs/LanguageExtensions.html#builtin-dump-struct
template<object T>
void dump(wstring& out, const T& obj);
// For primitives and pointers
template<typename T>
void dump(wstring& out, const T& var);
// For wide strings
inline void dump(wstring& out, const wstring& str);
// For narrow strings (converted to wide)
inline void dump(wstring& out, const string& str);Examples:
#include "dump.hpp"
#include <string>
struct Config {
int timeout = 3000;
std::string name = "server";
bool enabled = true;
};
int main() {
std::wstring out;
Config cfg;
dbg::dump(out, cfg);
// out = { timeout = 3000, name = "server", enabled = 1 }
dbg::dump(out, 42.5f);
// out = "{ ... } 42.5"
return 0;
}Formats objects into readable wide strings with container-aware formatting.
// For associative containers (unordered_map, map, ...)
template<container_pairs Cont>
void format(wstring& out, const Cont& cont);
// For linear containers (vector, deque, ...)
// This function is able to format any object that has
// `begin()`, `end()` iterators and `size()`, `empty()` methods
template<container_linear Cont>
void format(wstring& out, const Cont& cont);
// For anything else: your classes, structs, variables, ...
template<typename T>
void format(wstring& out, const T& obj);Formatting Styles:
| Category | Format | Example |
|---|---|---|
| Linear container | [ a, b, c ] |
[ 1, 2, 3, 4 ] |
| Associative container | { k -> v; ... } |
{ "one" -> 1; "two" -> 2 } |
| String | "text" |
"hello world" |
| Primitives | value |
42, 3.14 |
| Classes | { field1 = val, ... } |
{ x = 10, y = 20 } |
Examples:
#include "dump.hpp"
#include <vector>
#include <map>
#include <iostream>
int main() {
std::wstring out;
std::vector<int> nums {10, 20, 30};
dbg::format(out, nums);
// out += "[ 10, 20, 30 ]"
std::map<std::string, int> scores {
{"Alice", 95},
{"Bob", 87}
};
dbg::format(out, scores);
// out += { "Alice" -> 95; "Bob" -> 87 }
std::wprintf(L"%s\n", out.c_str());
return 0;
}Formats a narrow (ANSI) printf-style string and converts it to wide (UTF-16) using MultiByteToWideChar.
template<typename... Args>
void to_wide(wstring& out, const UINT codePage, const char* format, Args&& ...args);Parameters:
out– Target string to which converted text will be appended.codePage– Win32 code page (CP_UTF8,CP_ACP, etc.)format– printf-style format stringargs– Arguments corresponding toformat
Examples:
#include "dump.hpp"
#include <string>
int main() {
std::wstring out;
// Convert narrow string to wide
dbg::to_wide(out, CP_ACP, "Hello, %s!", "world");
// out = "Hello, world!"
// Format with numbers
dbg::to_wide(out, CP_ACP, " Value: %d", 42);
// out = "Hello, world! Value: 42"
// debugapi.h
// https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-outputdebugstringw
OutputDebugStringW(out.c_str());
return 0;
}#include "dump.hpp"
#include <string>
#include <vector>
struct Address {
int zip = 101000;
};
struct Person {
int age = 30;
Address addr;
std::vector<int> scores {95, 87, 92};
};
int main() {
Person p;
log(p);
/* Person = {
int age = 30
Address addr = {
int zip = 101000
}
std::vector<int> scores = [ 95, 87, 92 ]
}
*/
return 0;
}#include "dump.hpp"
#include <string>
class SecretData {
private:
int secret_value = 42;
double _internal = 3.14;
public:
int public_field = 100;
};
int main() {
SecretData data;
log(data);
/*
SecretData = {
int secret_value = 42
double _internal = 3.14
int public_field = 100
}
*/
return 0;
}#include "dump.hpp"
#include <string>
#include <iostream>
int main() {
std::wstring message;
// Format multiple values
int x = 10, y = 20;
dbg::to_wide(message, CP_ACP, "Position: (%d, %d)", x, y);
// message = "Position: (10, 20)"
// Output with wprintf
std::wprintf(L"%s\n", message.c_str());
// Or with OutputDebugStringW
OutputDebugStringW(message.c_str());
return 0;
}#include "dump.hpp"
#include <map>
#include <vector>
int main() {
std::map<std::string, std::vector<int>> matrix {
{"row1", {1, 2, 3}},
{"row2", {4, 5, 6}}
};
log(matrix);
/*
map = {
"row1" -> [ 1, 2, 3 ]
"row2" -> [ 4, 5, 6 ]
}
*/
return 0;
}#include "dump.hpp"
int main() {
// Disable all log() macro output
dbg::verbose = false;
int x = 42;
log(x); // No output
// You can still use format() and dump() directly
std::wstring out;
dbg::format(out, x);
// out = "42"
return 0;
}The library defines comprehensive C++23 concepts to ensure compile-time type safety:
// These concepts are available for custom extensions:
namespace dbg {
template<typename T> concept object; // Class, union, or enum
template<typename T> concept container; // Any STL-like container
template<typename T> concept container_pairs; // map, set, ...
template<typename T> concept container_linear; // vector, deque, ...
template<typename T> concept pair; // (key, value) pairs
}Combine dump() with custom formatting:
#include "dump.hpp"
#include <string>
struct Config {
int timeout = 3000;
std::string endpoint = "localhost";
};
int main() {
Config cfg;
std::wstring msg;
// Build custom debug message
dbg::to_wide(msg, CP_ACP, "[DEBUG] Config: ");
dbg::format(msg, cfg);
// msg = "[DEBUG] Config: { timeout = 3000, endpoint = "localhost" }"
std::wprintf(L"%s\n", msg.c_str());
return 0;
}The library uses function overloading and C++23 concepts, so you can add custom dbg::dump() and dbg::format() overloads for your types. The Clang compiler will automatically select the most constrained (strict) overload.
#include "dump.hpp"
#include <string>
struct MyMatrix {
int data[3][3];
size_t rows = 3;
size_t cols = 3;
};
namespace dbg {
template<>
inline void dump(wstring& out, const MyMatrix& m) {
out += L"[ ";
for (size_t i = 0; i < m.rows; ++i) {
for (size_t j = 0; j < m.cols; ++j) {
if (i > 0 || j > 0) out += L", ";
dump(out, m.data[i][j]);
}
}
out += L" ]";
}
}
int main() {
MyMatrix mat {{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
}};
log(mat);
// mat = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
return 0;
}Tip
Call dump() recursively for nested values - it will use the correct overload for each type.
#include "dump.hpp"
#include <string>
struct RingBuffer {
int buffer[64];
size_t size = 0;
size_t head = 0;
size_t tail = 0;
void push(int v) {
buffer[tail++] = v;
++size;
}
};
namespace dbg {
template<typename T>
concept ring_buffer = requires(T t) {
t.size;
t.buffer;
};
// Custom format
template<ring_buffer T>
void format(wstring& out, const T& rb) {
if (rb.size == 0) {
out += L"[]";
return;
}
out += L"[";
for (size_t i = 0; i < rb.size; ++i) {
if (i > 0) out += L", ";
dump(out, rb.buffer[(rb.head + i) % 64]);
}
out += L"]";
}
}
int main() {
RingBuffer rb;
rb.push(10); rb.push(20); rb.push(30);
log(rb);
// rb = [ 10, 20, 30 ]
return 0;
}This library is released under the MIT License. You can use it as you want. See LICENSE.txt for details.
Contributions are welcome! Please feel free to submit issues or pull requests to improve the library.

