Skip to content

Repository files navigation

Language: C++ License: MIT Header-only

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.

Quick Start

Installation

Copy dump.hpp to your include directory:

cp dump.hpp /path/to/your/include/

And #include it in your project:

#include "dump.hpp"

CMake 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

JetBrains 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()

Basic Usage

#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;
}

API Reference

log(obj) macro

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;

Main Functions

dump()

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;
}

format()

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;
}

to_wide()

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:

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;
}

Examples

Debugging Nested Structures

#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;
}

Debugging Private Fields

#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;
}

Wide String Formatting

#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;
}

Debugging Containers

#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;
}

Disabling Output

#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;
}

Advanced Usage

Concepts

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
}

Custom Debug Output

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;
}

Extending with Custom Overloads

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.

Custom dump() for Your Types

#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.

Custom format() for Containers

#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;
}

License

This library is released under the MIT License. You can use it as you want. See LICENSE.txt for details.

Contributing

Contributions are welcome! Please feel free to submit issues or pull requests to improve the library.

Contributors

Languages