Skip to content

Feature Request: Allow User-Defined Formatters for Non-String Pointer Types #4637

@eiclpy

Description

@eiclpy

Feature Request: Allow User-Defined Formatters for Non-String Pointer Types

Background

I've been using {fmt} extensively and really appreciate the library's flexibility and power. However, I've encountered a scenario where I'd like to define custom formatters for pointer types (excluding string-related pointers like char*, wchar_t*, etc.).

Use Case

In my codebase, I frequently work with raw pointers to arrays and multi-dimensional data structures. I've implemented a custom formatter that provides flexible formatting for these pointer types with features like:

  • Specifying the number of elements to format
  • Custom separators between elements
  • Support for nested array formatting
  • Dynamic element count through format arguments

Example Implementation

Here's a simplified version of what I've implemented:

// Concept to exclude string-related pointers
template <typename T>
concept __fmtable_ptr =
    !std::__is_one_of<std::remove_cv_t<T> *, std::nullptr_t, void *, char *,
                      wchar_t *, char16_t *, char32_t *>::value;

// Custom formatter template for pointers and arrays
template <typename T, int N> struct __pfmt {
  int vlen_or_argid;
  std::formatter<T> sub_fmt;
  std::string sep = ",";
  constexpr auto parse(std::format_parse_context &__pc) {
    const auto __last = __pc.end();
    auto __first = __pc.begin();
    auto __finished = [&] { return __first == __last || *__first == '}'; };
    // Parse element count (literal or dynamic via {})
    vlen_or_argid = N;
    if (!__finished()) {
      if (*__first == '{' && *(__first + 1) == '}') {
        vlen_or_argid = -__pc.next_arg_id();
        __first += 2;
      } else if (std::__format::__is_digit(*__first))
        std::tie(vlen_or_argid, __first) =
            std::__format::__parse_integer(__first, __last);
    }
    // Parse separator (with escape character '/')
    if (!__finished())
      sep.clear();
    while (!__finished() && *__first != ':') {
      if (*__first == '/') {
        __first++;
        if (__finished())
          throw std::format_error("Invalid format");
      }
      sep += *__first++;
    }
    // Parse sub-formatter for elements
    if (!__finished() && *__first == ':')
      __first++;
    __pc.advance_to(__first);
    __first = sub_fmt.parse(__pc);
    return __first;
  }
  auto format(const T *ptr, auto &ctx) const {
    size_t n = vlen_or_argid < 0
                   ? std::__format::__int_from_arg(ctx.arg(-vlen_or_argid))
                   : vlen_or_argid;
    for (size_t i = 0; i < n; ++i) {
      sub_fmt.format(ptr[i], ctx);
      if (i < n - 1)
        std::format_to(ctx.out(), "{}", sep);
    }
    return ctx.out();
  }
};

// Specialize formatters for non-string pointer types
template <__fmtable_ptr T> struct std::formatter<T *> : __pfmt<T, 0> {};
template <__fmtable_ptr T> struct std::formatter<const T *> : __pfmt<const T, 0> {};
template <__fmtable_ptr T, int N> struct std::formatter<T[N]> : __pfmt<T, N> {};
template <__fmtable_ptr T, int N> struct std::formatter<const T[N]> : __pfmt<const T, N> {};

Usage Examples

This enables powerful formatting syntax:

int vec[3] = {1, 2, 3};
std::format("{}", vec);           // Output: 1,2,3
std::format("{:2,:}", vec);       // Output: 1,2       (first 2 elements)
std::format("{:{},:}", vec, 2);   // Output: 1,2       (dynamic count)
std::format("{::02d}", vec);      // Output: 010203    (custom element format)
std::format("{:///::02d}", vec);  // Output: 01/:02/:03 (escaped separator)

// Nested arrays
int vec2[2][2] = {{1, 2}, {3, 4}};
std::format("{}", vec2);          // Output: 1,2,3,4
std::format("{:\n: :02d}", vec2); // Output: 01 02\n03 04 (formatted matrix)

Request

I understand that the C++ standard considers custom formatting for pointer types potentially ambiguous. However, while pointer formatting is often considered semantically ambiguous, the standard library does not explicitly forbid user-defined specializations of std::formatter<T*>, leaving this largely as a library design choice rather than a language-level restriction.

Would it be possible to ensure that {fmt} continues to allow user-defined formatters for non-string pointer types? This capability is incredibly valuable for working with numerical arrays, data structures, and debugging purposes.

I understand the library needs to balance flexibility with safety (hence excluding char* and similar types makes perfect sense), but allowing customization for other pointer types would be greatly appreciated.

Thank you for considering this request, and for all the excellent work on {fmt}!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions