archibate / debug-hpp

printing everything including STL containers without pain 🚀
44 stars 3 forks source link
cpp cpp-library cpp11 debugging formatting header-only single-header-library utility

debug.hpp

Tired of the dumb debugging of manually traversing containers and cout << them one by one? đŸĨĩ

Try this handy header-only library 🚀 prints everything including STL containers without pain! 🤩

English | įŽ€äŊ“中文 | Download

🎨 Usage

debug(), "my variable is", your_variable;

The above code prints:

your_file.cpp:233:  my variable is {1, 2, 3}

suppose your_variable is an std::vector

[!WARNING] debug() only works in Debug build! It is automatically disabled in Release build (we do this by checking whether the NDEBUG macro is defined). Yeah, completely no outputs in Release build, this is by design.

This is a feature for convenience: you don't have to busy removing all the debug() sentences after debug done, simply switch to Release build and everything debug is gone, no runtime overhead! And when you need debug simply switch back to Debug build and everything debug() you written before is back in life.

If you mean to use debug() even in Release build, just #define DEBUG_LEVEL 1 before including this header file to force enable debugging.

✨ Printing custom classes

struct Student {
    std::string name;
    int age;

    // typically we just return a tuple directly, and debug() will dump this class just like a tuple:
    auto repr() const {
        return std::make_tuple(name, age);
    }

    // alternatively, show in a format of {name: "name", age: 42}
    auto repr() const {
        return std::make_tuple(debug::named_member("name", name), debug::named_member("age", age));
    }

    // alternatively, let the macro provided by debug.hpp to generate a repr() method for you:
    DEBUG_REPR(name, age);

    // alternatively return a string directly:
    std::string repr() const {
        return "Student{name: " + name + " age: " + std::to_string(age) + "}";
    }

    // alternatively << to ostream directly:
    void repr(std::ostream &os) const {
        os << "Student{name: " << name << " age: " << age << "}";
    }
};

// if adding member function is not possible, you may also define repr as free function within the same namespace as Student (thanks to C++'s ADL mechanism):
inline auto repr(Student const &stu) {
    return std::make_tuple(name, age);
}

// global version for the DEBUG_REPR macro for generating repr as free function:
DEBUG_REPR_GLOBAL(Student, name, age);

// if your class was a template class while you have to define repr as free function...
DEBUG_REPR_GLOBAL_TEMPLATED(std::pair, (T1, T2), (class T1, class T2), name, age);

[!WARNING] make sure you have the const qualifier! otherwise debug() will refuse to invoke the repr function.

[!WARNING] For MSVC users: Please turn on /Zc:preprocessor before you could the DEBUG_REPR macros! This is a famous MSVC bug, not our fault.

🎁 Save debug output as string

auto s = static_cast<std::string>(debug(), "my variable is", your_variable);
// content of `s`: "your_file.cpp:233:  my variable is {1, 2, 3}"

auto s = static_cast<std::string>(debug().noloc(), "my variable is", your_variable);
// content of `s`: "my variable is {1, 2, 3}"

📝 Redirect debug output to spdlog

#define DEBUG_OUTPUT(x) spdlog::info(x)  // define this macro before including the header to customize where debug() output its result
#include "debug.hpp"

🚩 Assertion check

debug().check(some_variable) > 0;

Will trigger a 'trap' interrupt (debugbreak for MSVC and builtin_trap for GCC, configurable, see below) for the debugger to catch when some_variable > 0 is false, as well as printing human readable error message:

your_file.cpp:233:  assertion failed: 3 < 0

[!TIP] See test.cpp for more usage showcases.

🌠 Release build

After debugging complete, no need to busy removing all debug() calls! Simply:

#define NDEBUG

would supress all debug() prints and assertion checks, completely no runtime overhead. For CMake or Visual Studio users, simply switch to Release build would supress debug() prints. Since they automatically define NDEBUG for you in Release, RelWithDebInfo and MinSizeRel build types.

😏 Tested compilers

See https://godbolt.org/z/jYdj4T44n

🏅 To conclude

TL;DR: This is a useful debugging utility the C++ programmers had all dreamed of:

  1. print using the neat comma syntax, easy-to-use
  2. supports printing STL objects including string, vector, tuple, optional, variant, unique_ptr, type_info, error_code, and so on.
  3. just add a member method named repr, e.g. std::string repr() const { ... } to support printing your custom class!
  4. classes that are not supported to print will be shown in something like [TypeName@0xdeadbeaf] where 0xdeadbeaf is it's address.
  5. highly configurable, customize the behaviour by defining the DEBUG_xxx macros (see below)
  6. when debug done, supress all debug messages by simply #define NDEBUG, the whole library is disabled at compile-time, no runtime overhead
  7. Thread safe, every line of message is always distinct, no annoying interleaving output rushing into console (typical experience when using cout)

😜 Configurations

Here is a list of configurable macros, define them before including this header file to take effect:

💝 Questions?

What's your opinion for this handy printing utility? Any suggestions or feature request would be welcome in the GitHub issues!