-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generate optimal formatters at compile time #613
Comments
Perhaps I'm misunderstanding this issue, but is this even possible right now? From my brief attempts at playing around, it would seem you need constexpr function parameters to do this. You would somehow need to split a string in a function at compile-time, yet I know no valid way to achieve this:
The problem is this also needs to be able to run at run-time, which makes no sense for the template of std::array (or C arrays). Perhaps this is possible with odd manipulation of template character packs? I haven't figured out a proof-of-concept for that, though. |
It should be possible.
This is correct and {fmt} emulates them with some Line 3559 in 846c644
You can overload on |
A better solution will be enabled by C++20: |
A prototype of generating optimal formatters at compile time for a subset of std::format/{fmt}'s syntax by Hana: hanickadot/compile-time-regular-expressions@13afb2c |
In C++20 we will be able to do this: #include <string>
template <typename Char, size_t N>
class basic_fixed_string {
private:
Char data[N] = {};
public:
constexpr basic_fixed_string(const Char (&s)[N]) {
for (size_t i = 0; i < N; ++i)
data[i] = s[i];
}
};
template <typename Char, size_t N>
basic_fixed_string(const Char (&s)[N]) ->
basic_fixed_string<Char, N>;
template <basic_fixed_string, typename... Args>
std::string format(const Args&... args) {
return {};
}
auto s = format<"{}">(42); which is a pretty horrible API. Need to find a way to pass a compile-time string as an argument to make it usable. |
So far the most usable API is #include <string>
template <typename Char, Char...>
struct format_string {};
template <typename Char, Char... CHARS>
constexpr format_string<Char, CHARS...> operator""_f() {
return {};
}
template <typename S, typename... Args>
std::string format(const S&, const Args&... args) {
return {};
}
auto s = format("{}"_f, 42); but it requires non-standard UDL extension and passing each character as a separate template argument. |
With P1221 Parametric Expressions we could have the perfect API: #include <fmt/format.h>
template <typename... Args>
constexpr bool check_format_string(std::string_view format_str) {
return fmt::internal::do_check_format_string<
char, fmt::internal::error_handler, Args...>(format_str);
}
using format(constexpr auto format_str, auto... args) {
constexpr bool b =
check_format_string<decltype(args)...>(format_str);
return fmt::format(format_str, args...);
}
auto s = format("{}", 42); // format string checked at compile time |
Yes, but you also need to know the types of formatting arguments. UDL doesn't have access to that and I wasn't able to return a compile-time string from the UDL without splitting it into chars. This is as far as I got: https://godbolt.org/z/pMQUfP |
Yeah, I deleted my comment when I realized my mistake. |
@vitaut Hi Victor,
This syntax is a part of the C++ standard // numeric literal operator template
template <char...> auto operator "" _f(); Good news, I even see it in C++11:
A valid point though it requires the type to be |
No, it isn't. It is only valid for numeric literals, as the comment above the example states. |
@foonathan True, sorry for misleading. Either way, hopefully, the committee will do something about it. :) |
I am not sure why you think that, and intuitively I strongly disagree with you. It makes it clear that the format string is a compile-time parameter, and is a logical counterpart to the version that takes a run-time parameter. |
From my experience with users. They expect intuitive API Something like |
Unless something changed, Rust uses a macro for that:
So the syntax is different there, as well. I think that the template parameter syntax is fine and intuitive, TBH. Even more than a literal, which would require a using namespace anyway |
Sure and it is great because even a person not familiar with Rust can immediately understand that it is some sort of a call.
What's particularly sad is that C++ now has all this nice Hopefully C++23 will have some way to express this in a way that normal users can intuitively understand be it something like Rust macros or some other facility. |
@SuperV1234, also keep in mind that you are an expert in C++ so things that are intuitive to you may not be intuitive to vast majority of users. |
Anyone with basic C++ knowledge understands that Mentioning Rust is contradictory - there is a syntactic marker to show that this is not a regular run-time call, which is exactly what format("{}", foo); // Run-time call
format!("{}", foo); // Macro - operates on AST
This honestly makes no sense to me. Since the dawn of time, any C++ user that has basic knowledge of templates (i.e. everyone that knows how to use
The real WTF would be having a call that looks like it's run-time, but it's actually compile-time. People were mesmerized and confused by
Why would you want that? Rust got this right: provide syntactical markers to show what things are. If I have a macro, it should be visible on the call site. Similarly, if I have a compile-time parameter, it should be visible on the call site. The C++ way of passing compile-time parameters is and has always been I see no advantage in changing that.
Can you elaborate on this? Your example is pretty intuitive to me, following the rules I just mentioned: // v in round parenthesis, so this is a run-time parameter
format<"{}">(42)
// ^ in angle brackets, so this is a compile-time parameter It is clear for a user that, with the above syntax: int i = 42;
format<"{}">(i); // This works, because `i` is run-time and I am passing it in round parenthesis
std::string s{"{}"};
format<s>(42); // This can't work, because I cannot pass `s` to a compile-time parameter
Users intuitively understand that templates & template parameters mean compile-time. Since C++98, if you have template <int I> void foo(int j); it is obvious to everyone that |
What would be more intuitive than either suggested API? I think using templates for non-type parameters is a little unintuitive, but so are most things in any programming language. Certainly, It's reasonable if I didn't know as much C++ as I do. Also, I don't understand how this is less intuitive than rust's macros in this case. Using everything in parentheses has some implicit information to it, which may be more intuitive, but could also seem misleading in some situations I imagine. |
One has to learn templates to understand how to do such as trivial thing as formatting an integer and it still doesn't explain why pass a string as a template argument. This is exactly what I mean by "WTF C++ is complex". Pretty much any other language has an obvious API for this, even C. And
In case of
The marker is a red herring - the API is still obvious for almost anyone without any knowledge of macros or Rust. In the end there is still some formatting code executed at runtime which returns a string and that's all that matters.
I don't think this is how templates are understood. Templates are not synonymous with compile-time, they define a family of functions or classes.
Because I've been answering user questions for years and as I wrote people very much expect
Noone is changing that because that's not how things work.
Again, no. That's completely orthogonal concepts and hopefully noone teaches templates like that. Fortunately TMP is dying and we'll see less of this in the future. |
The problem is not with non-type template parameters per se. Those are great and there are some reasonable uses of them such as |
Thats a fair argument for that API, but my comment was towards your C++23 comment implying there could be something even better introduced by that language. |
my comment was towards your C++23 comment implying there could be
something even better introduced by that language.
Ah, I misunderstood the question, sorry. What I meant is C++23 can make
this API possible (I think there are several proposal that address that).
|
Something that has been lost in this recent discussion (and vitaut's twitter poll): But I'm also of the opinion that |
@mwinterb, that's a great point. |
If I understand P0732 correctly, then this should be valid C++20, although GCC and clang still implement it incompletely: #include <string>
// constexpr string type
template <int N>
class fixed_string {
private:
char data[N] = {};
public:
constexpr fixed_string(const char (&s)[N]) {
for (int i = 0; i < N; ++i) {
data[i] = s[i];
}
}
};
template <size_t N>
fixed_string(const char (&s)[N]) ->
fixed_string<N>;
// compile time string type
template <fixed_string>
struct ct_string{};
// user defined literal
template <fixed_string Str>
ct_string<Str> operator"" _cts();
// format overload
template <fixed_string Str, typename ... Args>
std::string format(ct_string<Str>, Args&& ... args) {
return {};
}
// usage
auto s = format("{}"_cts, 42); |
I think that the literal overload is non-Standard, but I am not 100% sure. Also, to be fair, the "usage" example needs to include a using namespace fmt::format::literals;
fmt::format("{}"_cts, 42);
// vs
fmt::format<"{}">(42); |
That is my understanding too. |
Had forgotten the I checked the last working draft of C++20 (n4810), [lex.ext] (5.13.8)p5 was adopted. The UDL is thus valid C++20. |
Thanks for checking, that's great news. |
Proof-of-concept implementation is there but needs more work to make it production-ready. Some of the issues are tracked in #1324. |
As pointed out by Louis Dionne in #546:
This can make the format API as efficient as the write API and eliminate the need in the latter.
The text was updated successfully, but these errors were encountered: