c++std-rangesc++23fmtstdformat

What is the ideal way to customize the separators and brackets for formatting ranges?


In C++23, we can use std::print directly to print ranges thanks to P2286:

std::vector v = {10, 2, 42, 15};
std::println("{::02X}", v); /* 02X is the format spec for elements of range */

which gives:

[0A, 02, 2A, 0F]

However, if I want to customize the brackets and separator to "|" and "-" respectively:

|0A-02-2A-0F|

How should I do this? I know that std::range_formatter provides two custom APIs, set_brackets() and set_separator(), so the intuitive way seems to specialize std::formatter:

template<class T, class charT>
struct std::formatter<std::vector<T>, charT> : std::range_formatter<T, charT> { 
  constexpr formatter() {
    this->set_brackets("|", "|");
    this->set_separator("-");
  }
};

which gives the expected results.

But I wonder if there is a best practice for this, such as customizing brackets and separators directly using specific formatting strings (which the standard currently doesn't seem to support).

What is the recommended way to do this in the current standard? Is there an issue with the resolution of the question?


Solution

  • The {fmt} library, std::format and std::print are based on, provides fmt::join which allows you to do this. For example:

    #include <vector>
    #include <fmt/ranges.h>
    
    int main() {
      std::vector v = {10, 2, 42, 15};
      fmt::println("|{:02X}|", fmt::join(v, "-"));
    }
    

    prints

    |0A-02-2A-0F|
    

    godbolt

    Unfortunately the C++ standard doesn't provide an equivalent to fmt::join but it is not difficult to implement one based on https://github.com/fmtlib/fmt/blob/df249d8ad3a9375f54919bdfa10a33ae5cba99a2/include/fmt/ranges.h#L620-L669.

    You shouldn't specialize formatter for types you don't own such as std::vector<T> because it would conflict with the standard ones.