String formatting for debugging

Some programming languages explicitly distinguish string formatting for debug purposes and string formatting for displaying to the user. In the case of Rust, it provides two separate traits - Debug and Display respectively. When there’s no obvious user-facing representation, Rust uses display adapters - the wrapper types like Escaped or Quoted that implement Display with a specific meaning.

Some languages, including C++, decide not to have such semantics in the standard library. I guess it depends on the design principles of the language. Rust prefers doing things in one way and imposes this principle to the programmer. C++ is the opposite. In a C++ program you have the option of choosing what approach to use to implement the formatting semantic. You can end up not choosing, but then your program will be mixing different approaches, because there are many available: operator<< overload, string conversion operator, std::to_string overload, std::format, and custom functions.

If you choose to go with std::format, then it is possible to use a formatting option to distinguish between the two semantics. For example, ‘d’ for debug and lack of ‘d’ for human readable string.

#include <print>

using namespace std;

struct Something {
    string_view field1;
    size_t field2;
    string_view debug_field2;
};

namespace std {

template <>
struct formatter<Something> {
    bool debug = false;

    constexpr auto parse(format_parse_context& ctx) {
        auto it = ctx.begin();

        if (*it == '}') {
            debug = false;
        } else if (*it == 'd') {
            debug = true;
        } else {
            throw std::format_error("Supported format options are: 'd' - debug; none - human readable");
        }

        return ++it;
    }

    template <typename FormatContext>
    auto format(Something const& x, FormatContext& ctx) const {
        if (debug) {
            return format_to(ctx.out(), "Something({}, {}, {})", x.field1, x.field2, x.debug_field2);
        } else {
            return format_to(ctx.out(), "Something({}, {})", x.field1, x.field2);            
        }
    }
};

}

int main() {
    Something x{"a", 1, "b"};
    println("{}", x);
    println("{:d}", x);
}

// cd /tmp && g++ -std=c++23 foo.cpp && ./a.out

If you work with non-standard strings, then a CPO function object per formatting semantic might be a good solution.

Swift have two formatting APIs: String(format: "%d", someVar) and "\(someVar)". The former delegates to Objective-C, the latter is implemented in Swift and relies on protocols ExpressibleByStringInterpolation, CustomStringConvertible, and StringInterplation type alias associated with the type for which ExpressibleByStringInterpolation is implemented. In the standard library String implements ExpressibleByStringInterpolation, enabling interpolation into strings, but you can enable this feature for your own string types too. One way to customize formatting for a custom type is to implement CustomStringConvertible.

Kotlin, similar to C++, doesn’t distinguish between display and debug string representations. To make your class formattable, you need to implement the toString method.

Visitor Counter