Skip to content
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

Merged

Conversation

alexezeder
Copy link
Contributor

@alexezeder alexezeder commented Dec 9, 2020

Extends #2019 by adding support of formatting specs for bool, integer, char and string:

  • fill <a character other than '{' or '}'>, like {:*>4}, {:ж^4}
  • align "<" | ">" | "^", like {:^4}
  • sign "+" | "-" | " ", like {:+}
  • "#", like {:#b}
  • "0", like {:04}
  • width integer | "{" "}", like {:4}, {:{}}
  • int_type "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 in compile-test updated a bit to use not std:: but fmt:string_view, this eliminates unnecessary include there

@alexezeder
Copy link
Contributor Author

alexezeder commented Dec 10, 2020

Also, looks like tests for formatting at compile-time (CompileTimeFormattingTest in compile-test.cc) do not run on CI because the GCC 10.2 defines __cplusplus less than 202002L, at least on my PC... and on Compiler Explorer too, so CI definitely doesn't run it.
I suggest adding define for this whole formatting at compile-time feature in the same manner as with other features, so if it's not predefined then set it by the expression we already have __cplusplus >= 202002L. Is it a good idea, should I do that in this PR or not?

Copy link
Contributor

@vitaut vitaut left a 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!

OutputIt format(OutputIt out, const Args&... args) const {
constexpr OutputIt format(OutputIt out, const Args&... args) {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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:

  1. why all or almost all formatters currently defined in {fmt} has non-const format() function?
  2. 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.

Copy link
Contributor

@vitaut vitaut Dec 20, 2020

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.

Copy link
Contributor Author

@alexezeder alexezeder Dec 22, 2020

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 formatters 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.

Copy link
Contributor

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.

include/fmt/compile.h Outdated Show resolved Hide resolved
include/fmt/core.h Show resolved Hide resolved
include/fmt/core.h Outdated Show resolved Hide resolved
include/fmt/core.h Show resolved Hide resolved
Comment on lines -1673 to +1677
FMT_INLINE basic_format_args(const format_arg_store<Context, Args...>& store)
constexpr basic_format_args(const format_arg_store<Context, Args...>& store)
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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...

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done #2075

@vitaut
Copy link
Contributor

vitaut commented Dec 20, 2020

I suggest adding define for this whole formatting at compile-time feature in the same manner as with other features, so if it's not predefined then set it by the expression we already have __cplusplus >= 202002L. Is it a good idea, should I do that in this PR or not?

We shouldn't be adding a macro for this. Instead __cplusplus >= 202002L should be replaced with a more specific check.

OutputIt format(OutputIt out, const Args&... args) const {
constexpr OutputIt format(OutputIt out, const Args&... args) {
Copy link
Contributor

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.

include/fmt/core.h Outdated Show resolved Hide resolved
include/fmt/core.h Show resolved Hide resolved
include/fmt/core.h Outdated Show resolved Hide resolved
Comment on lines 1146 to 1148
const char* digits = upper ? "0123456789ABCDEF"
: (is_constant_evaluated() ? "0123456789abcdef"
: data::hex_digits);
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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.

Comment on lines 1572 to 1573
auto* shifts = align == align::left ? data::left_padding_shifts
: data::right_padding_shifts;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with shifts.

}

template <typename FormatContext>
FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx)
Copy link
Contributor

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.

Copy link
Contributor Author

@alexezeder alexezeder Dec 23, 2020

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.

Copy link
Contributor

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.

@vitaut vitaut merged commit bbd6ed5 into fmtlib:master Dec 25, 2020
@vitaut
Copy link
Contributor

vitaut commented Dec 25, 2020

Thank you!

@alexezeder
Copy link
Contributor Author

But there is still the problem with FMT_INLINE...

@vitaut
Copy link
Contributor

vitaut commented Dec 25, 2020

But there is still the problem with FMT_INLINE.

I thought you are addressing this in a separate PR.

@alexezeder
Copy link
Contributor Author

alexezeder commented Dec 25, 2020

Yes, but in this PR some of them is missing due to a need for FMT_CONSTEXPR, but I will probably revert them back in that separate PR.
I just hoped that that PR would be merged before this 🙂.

alexezeder added a commit to alexezeder/fmt that referenced this pull request Dec 27, 2020
@alexezeder alexezeder deleted the feature/formatting_at_compile_time_specs branch January 15, 2021 21:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants