(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
vformatinstead 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
constevalconstructor.