(Since I am working with c++20 I am relying on range-v3
for my ranges!)
I am trying to build a range view which pipes tuples (e.g. from a zip_view
) to fmt::format automatically. The syntax should be intuitive, e.g.:
std::vector v1{"a", "b", "c", "d"};
std::vector v2{"alpha", "beta", "gamma", "delta"};
fmt::print(
"",
fmt::join(views::zip(v1, v2) | views::format("{}: {}"), "\n")
);
I know this can be achieved with a simple custom view::transform
which extracts the relevant tuple args and passes them manually to fmt::format but I would like to eliminate this boilerplate. My attempts are:
#define FWD(x) std::forward<decltype(x)>(x)
#define FWD_UNPACK(x) std::forward<decltype(x)>(x)...
template <auto str>
struct formatter {
constexpr auto operator()(auto&&... args) const {
return fmt::format(str, FWD_UNPACK(args));
}
};
namespace ranges::views {
consteval auto format(auto&& format_str) {
constexpr auto to_format = [=](auto&& args_tuple) {
return std::apply(
formatter<format_str>{},
FWD(args_tuple));
};
return transform(to_format);
}
}
I have made format
consteval to ensure that format_str
would be a constant-time variable, since fmt::format
requires a constexpr format string to be passed as first argument. However, I am getting the error:
<source>:25:23: error: non-type template argument is not a constant expression
25 | formatter<format_str>{},
(AUTHOR edit: This approach was never going to work <-- consteval functions do not make the parameters constexpr, see e.g. this SO post and the related proposal for this language feature)
So as a 2nd attempt I conceded an uglier syntax for a working method:
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template <StringLiteral str>
struct formatter {
constexpr auto operator()(auto&&... args) const {
return fmt::format(str.value, FWD_UNPACK(args));
}
};
template <StringLiteral format_str>
consteval auto format2() {
constexpr auto to_format = [=](auto&& args_tuple) {
return std::apply(formatter<format_str>{}, FWD(args_tuple));
};
return transform(to_format);
}
which would need to be called like
fmt::println(
"{}", fmt::join(views::zip(v1, v2) | views::format2<"{}: {}">(), "\n"));
which works and prints
a: alpha
b: beta
c: gamma
d: delta
but I am not happy with the unintuitive syntax of it.
Everything is captured in this godbolt example https://godbolt.org/z/x9G96Mncs.
Can someone explain how to make view::format("{}: {}")
work?
It's easy to accomplish if you don't need to check the format string at compile time. Just use
vformat
instead offormat
.But it is much tricker if you do want to add compile-time checking, although it can be done by performing the check in a
consteval
constructor.