-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Add support of most format_specs for formatting at compile-time #2056
Add support of most format_specs for formatting at compile-time #2056
Conversation
Also, looks like tests for formatting at compile-time ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR!
include/fmt/compile.h
Outdated
OutputIt format(OutputIt out, const Args&... args) const { | ||
constexpr OutputIt format(OutputIt out, const Args&... args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly is the problem with const
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All these problems started with mutable
, GCC thinks that mutable formatter<T, Char> fmt
in spec_field
structure cannot be used at compile-time. As I can see Clang <7.0 also thinks the same way, this is why the side effect of provided PR is that Clang <7.0 with C++17 support can also use FMT_COMPILE
with specs.
I failed to get a small code example which raises this error, I tried to create a failing example by recreating all significant parts of the error stack trace, but strangely it works. But I can present you the huge example with this error, this example is created by combining all needed headers of {fmt} together (version from this PR, except compile.h
) and by making everything needed as constexpr
in compile.h
without mutable
-related changes. Here it is on Compiler Explorer: https://godbolt.org/z/n1Kz38
Since the behaviour of GCC 10.2 and Clang <7.0 is kind of similar, I also tried to recreate the error with Clang 6.0 with my small code example that IMHO should fail - no luck, so looks like I'm missing something here. But of course, it fails to compile with a bit modified huge example where all {fmt} headers are combined: https://godbolt.org/z/P17TGG
So, this became non-const to eliminate mutable
, const CompiledFormat& cf
became non-const references because method format
is no more const
, constexpr auto compiled
became just auto compiled
because const object cannot be passed via non-const reference. This is my explanation for why I made these changes, of course, I can miss some more elegant solution, especially considering that I failed to recreate this error in a small example. In case it's true and more elegant solution exist I'll be glad to implement it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the correct solution is to make formatter::format
const and keep this format
function const.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, by in my opinion, there is a potential problem with this solution, with making format()
function in formatters const - maybe if a some of formatters have format()
function non-const on purpose then it cannot be used at compile-time. I just don't know:
- why all or almost all formatters currently defined in {fmt} has non-const
format()
function? - how rare it is that
format()
function in custom formatters is non-const on purpose?
I mean, I can definitely try to make this function const for formatters which are used in test, but since you have better perspective here it will be helpful if you agree or disagree with me for this potential problem.
Actually, I don't even consider to go this way because some already created formatters would be unavailable at compile-time. On the other hand the code I have in this PR right now does not change formatters at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why all or almost all formatters currently defined in {fmt} has non-const format() function?
No good reason, just less typing =).
how rare it is that format() function in custom formatters is non-const on purpose?
I don't think it should necessary at all. format
shouldn't mutate formatter
, only apply format specifiers stored in it to the input (or rather output =)).
already created formatters would be unavailable at compile-time
We can fix this for all {fmt} formatters and later provide a workaround for user-defined ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I reverted back most of the changes in compile.h
and mark all of formatter
s as const, at least those I found.
and later provide a workaround for user-defined ones.
But there is no such thing as workarounds at compile-time 🙂, that why it's so cool to check your code just by making it execute at compile-time. Of course, I'm talking about workarounds that are for removing the constness of objects, not interface workarounds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workaround is to copy the formatter when needed.
FMT_INLINE basic_format_args(const format_arg_store<Context, Args...>& store) | ||
constexpr basic_format_args(const format_arg_store<Context, Args...>& store) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here and elsewhere: please keep FMT_INLINE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I missed that FMT_INLINE
has an attribute inside, and this line can use FMT_INLINE
and constexpr
. But those lines where FMT_INLINE
was replaced by FMT_CONSTEXPR(20)
will have double inline
specifier, which makes some compilers unhappy and some very unhappy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So looks like one of these macros shouldn't set inline
and that would be really strange if this macro is going to be FMT_INLINE
.
I can give an example of Hedley, it does not fallback to inline
in the case where constexpr
is not available - https://github.com/nemequ/hedley/blob/5e50f6b735aeb4e09e3b2ad3d1717db3c0613bfa/hedley.h#L1269-L1276
and I think it's right because I can always mark a function as inline
with or without constexpr
, no matter that constexpr
implies inline
.
In case you agree with me here I can create a separate PR to remove inline
from FMT_CONSTEXPR(20)
macro. Otherwise I just don't know how to fix these problems without introducing FMT_CONSTEXPR(20)_NO_INLINE
macro, which probably would be also bad naming because there is FMT_NOINLINE
macro that expands to additional attribute...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case you agree with me here I can create a separate PR to remove inline from FMT_CONSTEXPR(20) macro.
Sure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done #2075
We shouldn't be adding a macro for this. Instead |
include/fmt/compile.h
Outdated
OutputIt format(OutputIt out, const Args&... args) const { | ||
constexpr OutputIt format(OutputIt out, const Args&... args) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the correct solution is to make formatter::format
const and keep this format
function const.
for `is_constant_evaluated()` function
to revert back some changes in compile.h
include/fmt/format.h
Outdated
const char* digits = upper ? "0123456789ABCDEF" | ||
: (is_constant_evaluated() ? "0123456789abcdef" | ||
: data::hex_digits); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we make data::hex_digits constexpr instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But I tried to make them available both at compile-time and at runtime via macro definitions. Right now they are embedding into the static library, so I don't even touch them in this PR to not break the ABI. Maybe such variables should be declared as constexpr
and be copied to that static data structure for runtime uses and still be chosen for compile-time uses.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, we can go the same way as with digits
- by implementing cast specially for compile-time. It's going to be more complex, but it's possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean defining it as
static constexpr char hex_digits[] = "0123456789abcdef";
in the basic_data
struct. This is a tiny piece of data so we don't need to worry about having it in the header.
include/fmt/format.h
Outdated
auto* shifts = align == align::left ? data::left_padding_shifts | ||
: data::right_padding_shifts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same with shifts.
include/fmt/format.h
Outdated
} | ||
|
||
template <typename FormatContext> | ||
FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
non-const overload is not needed, please remove.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The const one copies the specs_
member into a temporary variable to be able to modify it, while the non-const one just uses this member field. So this change can introduce performance loss if this copy is heavy enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, we can optimize this later.
for functions where `hex_digits` or shifts from `basic_data` is used
Thank you! |
But there is still the problem with |
I thought you are addressing this in a separate PR. |
Yes, but in this PR some of them is missing due to a need for |
Extends #2019 by adding support of formatting specs for bool, integer, char and string:
<a character other than '{' or '}'>
, like{:*>4}
,{:ж^4}
"<" | ">" | "^"
, like{:^4}
"+" | "-" | " "
, like{:+}
"#"
, like{:#b}
"0"
, like{:04}
integer | "{" "}"
, like{:4}
,{:{}}
"b" | "B" | "d" | "o" | "x" | "X"
, like{:b}
,{:x}
,{:X}
.I think that bool, integer, char and string formatting should be fully supported at compile-time, of course, if there are no caveats I'm not aware of. Floating-point formatting is not addressed by this PR, so it's still unavailable at compile-time.
Also
test_string
incompile-test
updated a bit to use notstd::
butfmt:string_view
, this eliminates unnecessary include there