Better_Software_Header_Mobile Better_Software_Header_Web

Find what you need - explore our website and developer resources

Indented Printing with fmt

Pretty printing of nested structs in C++

struct Vec2 {
    int x;
    int y;
};

struct Size {
    int width;
    int height;
};

struct Rect {
    Vec2 position;
    Size size;
};

...
Rect rect{ { 0, 20 }, { 800, 600 } };
spdlog::info("rect: {}", rect);
rect: Rect2 {
    position: Vec2 {
        x: 0
        y: 20
    }
    size: Size2 {
        width: 800
        height: 600
    }
}
auto format(auto v, fmt::format_context &ctx) const
        -> fmt::format_context::iterator
{
    const T &derived = static_cast<const T &>(\*this);
    fmt::format_to(ctx.out(), "{} {{\\n", derived.name());

    // Get indentation level and output members
    ...

    // Output the closing curly brace and return the context iterator
    ...
}
auto format(auto v, fmt::format_context &ctx) const
        -> fmt::format_context::iterator
{
    // Output type name
    ...

    // Extract indentation level to use
    size_t indentToUse;
    if (!hasArgumentIndex) {
        // Implicit argument ordering - use indent from parse() function
        indentToUse = indent;
    } else {
        // Numbered argument ordering
        auto argValue = ctx.arg(argumentIndex);
        auto extractSizeT = \[\]<typename U>(const U &v) -> size_t {
            if constexpr (std::is_integral_v<U>) {
                return v;
            } else {
                throw std::format_error("Wrong argument datatype provided");
                return {};
            }
        };

        indentToUse = fmt::visit_format_arg(extractSizeT, argValue);
    }

    // Output members and closing brace
    ...
}
auto format(auto v, fmt::format_context &ctx) const
        -> fmt::format_context::iterator
{
    // Output type name
    ...

    // Extract indentation level to use
    ...

    // Output members and closing brace
    memberIndent = indentToUse + 4; // Yes it's a magic number
    derived.format_members(v, ctx);

    // Output closing curly brace
    ...
}
void format_scalar(std::string_view label, auto v, fmt::format_context &ctx) const
{
    fmt::format_to(ctx.out(), "{:{}}{}: {}\\n", "", memberIndent, label, v);
}

void format_struct(std::string_view label, auto v, fmt::format_context &ctx) const
{
    fmt::format_to(ctx.out(), "{:{}}{}: {:{}}\\n", "", memberIndent, label, v, memberIndent);
}

Substitutions for format_scalar

Substitutions for format_struct

auto format(auto v, fmt::format_context &ctx) const
        -> fmt::format_context::iterator
{
    // Output type name
    ...

    // Extract indentation level to use
    ...

    // Output members and closing brace
    ...

    // Output closing curly brace
    return fmt::format_to(ctx.out(), "{:{}}}}", "", indentToUse);
}
template<>
struct fmt::formatter<Vec2> : indenting_formatter<fmt::formatter<Vec2>> {
    auto name() const -> std::string_view { return "Vec2"; }

    auto format_members(Vec2 v, fmt::format_context &ctx) const
            -> fmt::format_context::iterator
    {
        format_scalar("x", v.x, ctx);
        format_scalar("y", v.y, ctx);
        return ctx.out();
    }
};

About KDAB

SeanHarmer

Sean Harmer

Managing Director KDAB UK

Sign up for the KDAB Newsletter

Learn Modern C++

Learn more