Reputation: 67
I'm trying to specify a formatter to print mdspan
of rank 2 as a matrix, quite useful. I would like at least for element formatting to work, e.g. std::println("{::.2e}", matrix)
would print some double matrix with format .2e
for the doubles. The difficulty is this has to be done at compile time, I don't understand how this is generally done, "{:" + std::string{spec} + "}"
here is not compile time so it fails. Would be useful to see how this is done or get general tips for implementing formatting.
#include <print>
#include <mdspan>
#include <format>
template<class T, class Extents, class Layout, class Accessor>
requires (Extents::rank() == 2)
struct std::formatter<std::mdspan<T, Extents, Layout, Accessor>> {
std::string_view spec;
constexpr auto parse(format_parse_context& ctx) {
auto it = ctx.begin();
auto end = ctx.end();
spec = std::string_view{it, end};
return end;
}
auto format(const std::mdspan<T, Extents, Layout, Accessor>& md, format_context& ctx) const {
auto out = ctx.out();
*out++ = '[';
for (size_t i = 0; i < md.extent(0); ++i) {
if (i > 0) {
*out++ = '\n';
*out++ = ' ';
}
*out++ = '[';
for (size_t j = 0; j < md.extent(1); ++j) {
if (j > 0)
*out++ = ' ';
if (spec.empty())
out = std::format_to(out, "{}", md[i, j]);
else
out = std::format_to(out, "{:" + std::string{spec} + "}", md[i, j]);
}
*out++ = ']';
}
*out++ = ']';
return out;
}
};
int main() {
std::println("{::.2e}", std::mdspan((double []) {1.1111, 2, 3, 4}, 2, 2));
}
Upvotes: 1
Views: 93
Reputation: 681
In the past it worked best for me to use as much already implemented functionality as possible, especially when it comes to std::formatter
. One way to avoid having to do the heavy lifting would be the following changes:
std::formatter<T>
to benefit from an already implemented parse
functionallity.std::formatter<T>::format(md[i,j], ctx);
to take care of the number formatting and also advance the context output iterator.A working example could look like this:
#include <print>
#include <mdspan>
#include <format>
template<class T, class Extents, class Layout, class Accessor>
requires (Extents::rank() == 2)
struct std::formatter<std::mdspan<T, Extents, Layout, Accessor>> : std::formatter<T>
{
auto format(const std::mdspan<T, Extents, Layout, Accessor> & md, format_context & ctx) const {
auto out = ctx.out();
*out++ = '[';
for (size_t i = 0; i < md.extent(0); ++i)
{
if (i > 0)
{
*out++ = '\n';
*out++ = ' ';
}
*out++ = '[';
for (size_t j = 0; j < md.extent(1); ++j)
{
if (j > 0)
{
*out++ = ' ';
}
std::formatter<T>::format(md[i, j], ctx);
}
*out++ = ']';
}
*out++ = ']';
return out;
}
};
int main()
{
auto arr = std::array{ 1.1111, 2.0, 3.0, 4.0 };
auto span = std::mdspan(arr.data(), 2, 2);
std::println("{:.2e}", span);
}
Additionally, the iterator returned by parse
should return to ctx.begin()
if .begin() == .end()
- if not it then it should point to the first unmatched character if it is }
, otherwise it should throw a format_error
. Your implementation points beyond the last character and will/might lead to compile errors.
Upvotes: 1