-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Description
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}!