Compare commits

..

347 Commits

Author SHA1 Message Date
Victor Zverovich
a6e871e39b Don't pull in locale dependency 2025-12-04 06:28:37 -08:00
Watal M. Iwasaki
e137db699a
Fix doc CSS to display white-space properly (#4622)
See fmtlib/fmt.dev#25
2025-12-03 11:35:04 -08:00
dependabot[bot]
d6712ff2c0
Bump actions/checkout from 5.0.0 to 6.0.0 (#4621)
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](08c6903cd8...1af3b93b68)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-12-01 09:04:20 -08:00
Victor Zverovich
2d839bbc61 Fix format_to_n 2025-11-30 07:12:48 -08:00
Victor Zverovich
5bc56e24a9 Update clang-format to version 21 2025-11-28 20:05:18 -08:00
bigmoonbit
2727215c11
chore: minor improvement for docs (#4616)
Signed-off-by: bigmoonbit <bigmoonbit@outlook.com>
2025-11-28 12:55:25 -08:00
Victor Zverovich
790b9389ae
Update README for clarity on performance comparison 2025-11-23 11:15:23 -08:00
Victor Zverovich
a731c73fd5 Make FMT_USE_FULL_CACHE_DRAGONBOX depend on __OPTIMIZE_SIZE__ by default
Thanks to Matthias Kretz for the idea.
2025-11-23 09:03:02 -08:00
Victor Zverovich
3391f9e992 Cleanup test 2025-11-22 19:14:32 -08:00
friedkeenan
9f197b22ae
Make FMT_STRING redundant when FMT_USE_CONSTEVAL is enabled (#4612) 2025-11-22 14:36:29 -08:00
Victor Zverovich
f20b16617e Fix formatting 2025-11-22 13:48:03 -08:00
Victor Zverovich
14451704d5 Opt out std::complex from tuple formatting 2025-11-22 08:23:11 -08:00
Victor Zverovich
7a0da1c68a Merge compile-time FP tests into other compile-time tests 2025-11-22 07:46:38 -08:00
Victor Zverovich
e3d2174b3c Remove more invalid tests 2025-11-22 07:30:11 -08:00
Victor Zverovich
a6fb4d3b06 Remove invalid tests 2025-11-22 07:00:06 -08:00
Victor Zverovich
706fecea30 Apply clang-format 2025-11-22 06:45:50 -08:00
Victor Zverovich
e00fd756cc
Update link 2025-11-19 06:58:44 -08:00
Victor Zverovich
fc17e825d9 Remove the broken fmt::say function 2025-11-12 11:06:01 -08:00
Victor Zverovich
3fccfb8a80
Update benchmark table formatting in README 2025-11-07 10:05:56 -08:00
Victor Zverovich
690c9c71a0
Update benchmark results 2025-11-06 17:25:24 -08:00
Victor Zverovich
1122268510 Make path formatting lossless with WTF-8 2025-11-05 10:59:06 -10:00
Victor Zverovich
a4c7e17133 Improve build speed, take 2 2025-11-04 10:19:39 -10:00
Victor Zverovich
bfd0129b91 Improve build speed 2025-11-04 09:33:18 -10:00
Victor Zverovich
62f57b2496 Fix the macOS build 2025-11-04 09:12:57 -10:00
Victor Zverovich
ef7a566413 Reduce bloat-test result in debug mode from ~200k to ~85k 2025-11-03 16:21:10 -10:00
Victor Zverovich
42840cb415
Update benchmark results 2025-11-03 16:50:43 -08:00
Victor Zverovich
5ac44cd128 Fix a warning 2025-11-03 13:39:29 -10:00
Victor Zverovich
23c13b3060 Handle ulink in docs 2025-11-03 13:14:48 -10:00
Victor Zverovich
29c46fb82d Add support for more doxygen tags 2025-11-03 10:57:41 -10:00
Victor Zverovich
a0571f3f59 Document output_file 2025-11-03 10:11:31 -10:00
dependabot[bot]
a195dd6b37
Bump github/codeql-action from 3.30.5 to 4.31.2 (#4599)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.30.5 to 4.31.2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](3599b3baa1...0499de31b9)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.31.2
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-03 08:53:27 -08:00
Victor Zverovich
33ad559eb8 Fix fuzzer 2025-11-02 07:52:16 -10:00
dependabot[bot]
b6cd356196
Bump actions/upload-artifact from 4.6.0 to 5.0.0 (#4598)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.6.0 to 5.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](65c4c4a1dd...330a01c490)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-01 09:39:15 -07:00
J. Berger
c3be070b7e
When using MSVC x86 to compile v12.0.0 or v12.1.0, conversions from __int64 to a 32bit unsigned int trigger warnings. (#4594)
This is a follow-up for PR #4572.
2025-11-01 09:38:30 -07:00
Stéén
27bf8b47fe
Add FMT_CONSTEXPR to static_format_result members (#4591)
Co-authored-by: Robin Oger <roger@qcify.com>
2025-10-29 18:34:42 -07:00
Victor Zverovich
407c905e45 Update version 2025-10-29 07:40:27 -07:00
Victor Zverovich
f781d2b932
Update ChangeLog.md 2025-10-25 09:10:24 -07:00
Victor Zverovich
5987082c47 Bump version 2025-10-25 08:30:50 -07:00
Victor Zverovich
681c9e689b Update changelog 2025-10-25 08:30:50 -07:00
Peter Hill
913507044b
Fix leaky diagnostic ignored pragma (#4588)
Ignoring the `-Wconversion` diagnostic in `make_format_args` was
leaking out of the header, resulting in that warning being ignored in
downstream code that includes `fmt/base.h`.

Instead, we should `push`/`pop` the diagnostics to ensure this is
cleaned up.
2025-10-25 08:28:24 -07:00
Victor Zverovich
ff357e9e4a Remove extra whitespace 2025-10-25 08:10:57 -07:00
Victor Zverovich
728dfeab5b Fix apidoc comment 2025-10-25 08:06:23 -07:00
Victor Zverovich
7adc922ebb Update changelog 2025-10-25 08:02:24 -07:00
Victor Zverovich
70ed0ab82a Update changelog 2025-10-25 07:51:29 -07:00
Victor Zverovich
b95fd6132b Avoid an ABI break for clang 2025-10-25 07:17:41 -07:00
Victor Zverovich
7241bbb149 Update changelog 2025-10-22 15:32:15 -07:00
Victor Zverovich
c0ddbacfd3 Bump version 2025-10-22 15:32:15 -07:00
Victor Zverovich
9395ef5fcb Don't include std::locale::collate value in the symbol 2025-10-22 13:48:13 -07:00
Victor Zverovich
9721d974fc Workaround ABI compatibility between clang and gcc 2025-10-22 12:34:47 -07:00
Fatih BAKIR
d6bdb69c62
Move FMT_API from ostream class to members (#4584)
Putting FMT_API on the class definition propagates it to the base class
detail::buffer<char>'s members. However, MSVC not emit definitions for
inline members unless it sees the symbols as FMT_API when compiling.

This fix removes the FMT_API declaration from the class itself and marks
individual non-inline members as FMT_API to address the issue.

Fixes https://github.com/fmtlib/fmt/issues/4576
2025-10-20 07:49:23 -07:00
Victor Zverovich
08d38d6e78 Make error_code formatter debug-enabled 2025-10-19 10:35:45 -07:00
Victor Zverovich
a2289b8593 Fix the build 2025-10-19 10:30:42 -07:00
Victor Zverovich
e8da5ba275 Fix formatting 2025-10-19 10:22:39 -07:00
Victor Zverovich
e2aa06cd0a Workaround ABI incompatibility between clang ang gcc 2025-10-19 08:40:13 -07:00
Vladislav Shchapov
85f6ecc7a0
Add format_as support for std::variant and std::expected formatters (#4575)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-10-18 14:05:21 -07:00
Harry Denholm
378a5ab3c1
value-initialise the buffer array member of detail::ansi_color_escape so that it can be used in a constexpr context in MSVC; compiler rejects as non-constant due to 'uninitialized symbol' otherwise (#4581)
Co-authored-by: Harry Denholm <ishani@users.noreply.github.com>
2025-10-17 08:10:56 -07:00
John Zimmerman
656d14db8b
docs: format two unescaped printf references with backticks (#4578) 2025-10-16 11:27:51 -07:00
Justin Riddell
31bed1bb65
Fix module fmt::join<string_view> (#4577)
FMT_USE_STRING_VIEW define was incorrectly not being defined when using
modules, so there was no appropriate formatter specialization for
std::string_view
Fixes #4379
2025-10-16 11:25:19 -07:00
LiangHu
8eebb4334b
[fix] #4565 When using MSVC to compile v12.0.0, many compilation warn… (#4572) 2025-10-12 10:56:13 -07:00
Victor Zverovich
beefc1c14f Tweak wording 2025-10-12 10:54:28 -07:00
Victor Zverovich
81fe170849 Update docs 2025-10-12 10:42:12 -07:00
Victor Zverovich
491dc16a6d Cleanup docs 2025-10-12 10:33:05 -07:00
Victor Zverovich
41326207c7 Cleanup docs 2025-10-12 10:20:16 -07:00
Victor Zverovich
c4f70ab69e Cleanup docs 2025-10-12 09:34:46 -07:00
Victor Zverovich
5e214f0c43 Cleanup docs 2025-10-12 09:24:58 -07:00
Victor Zverovich
1234bc312e Apply clang-tidy 2025-10-11 07:58:17 -07:00
rohitsutreja
b77a751625
Revert std::malloc/std::free to global malloc/free (#4569) (#4570) 2025-10-10 09:37:05 -07:00
rohitsutreja
8db24c0ea9
restore 'inline' for normalize_libcxx_inline_namespaces (#4571) 2025-10-10 07:49:53 -07:00
Fatih BAKIR
03c7e28965
write_demangled_name supports libc++ + clang-cl (#4560)
The current implementation assumes whenever we're on an FMT_MSC_VERSION
compiler, the standard library is MSVC's STL. However, with clang-cl we
have the possibility of using LLVM libc++ instead of MSVC STL. In that
scenario, the previous implementation produced the wrong demangled names
for RTTI types.

This patch detects the different combinations, and combines the existing
demangling implementations to produce the correct names and make all
tests pass on libc++ + clang-cl.
2025-10-08 12:23:57 -07:00
Victor Zverovich
47a18b2fe9 Minor cleanup 2025-10-07 13:52:59 -07:00
Peter Steneteg
eb44d87b52
docs: range formatter grammar fix (#4567) 2025-10-07 11:34:00 -07:00
Martin Valgur
b580360ab7
base.h: _BitInt is not available when Clang is used as a host compiler for NVCC (#4564) 2025-10-06 08:45:12 -07:00
Oleksandr Koval
486e7ba579
support std::optional holding cv-qualified types (#4562)
Fixes #4561
2025-10-03 04:40:44 -07:00
Victor Zverovich
27ea09836a Error on unsafe uses of join 2025-10-02 16:24:22 -04:00
Victor Zverovich
b5b9317a3c Warn about unsafe use of join 2025-10-02 12:35:07 -04:00
dependabot[bot]
d13e5d048d
Bump github/codeql-action from 3.29.7 to 3.30.5 (#4558)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.29.7 to 3.30.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](51f77329af...3599b3baa1)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.30.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-10-02 06:08:07 -07:00
teruyamato0731
b9ac8b225d
style: Correct indentation in locale initialization (#4557) 2025-10-02 04:10:31 -07:00
teruyamato0731
4801f54e59
docs: Add compile-time options to API documentation (#4551) 2025-09-30 14:03:11 -07:00
Victor Zverovich
5f66e07cb0 Suppress an unused argument warning 2025-09-28 19:45:41 -04:00
Victor Zverovich
17be91c079 Fix a clang-tidy error 2025-09-26 08:28:51 -07:00
Victor Zverovich
dcea616535 Fix compilation with locale disabled in header-only mode 2025-09-26 07:48:43 -07:00
SnapperTT
a2fd48e039
Make FMT_USE_CONSTEVAL optional #4545 (#4546) 2025-09-24 11:00:41 -07:00
Nikita Tsarev
e28c371c0f
Fix ambiguous call to fprintf when compiling as a C++20 module (#4548) 2025-09-22 11:58:59 -07:00
Victor Zverovich
6b6cdd9405 Store size in a local variable while unchanged 2025-09-21 12:51:06 -07:00
Victor Zverovich
c5e55972ae Minor improvements to mkdocs 2025-09-21 12:47:15 -07:00
Victor Zverovich
dc409ee86d Explain mkdocs deploy invocation 2025-09-21 12:47:09 -07:00
Yuwei Zhao
4cce5f458d
Perf: Optimize function append in include/fmt/base.h (#4541) 2025-09-21 12:40:26 -07:00
Victor Zverovich
aa8a30838a Fix mike invocation 2025-09-21 08:03:21 -07:00
CrackedMatter
b18ece7d71
Export is_compiled_string and operator""_cf (#4544) 2025-09-19 08:54:54 -07:00
Victor Zverovich
e424e3f2e6 Update version 2025-09-17 09:45:35 -07:00
Victor Zverovich
ddd20d57ec Bump version 2025-09-17 09:14:47 -07:00
Victor Zverovich
594143e374
Fix formatting in ChangeLog benchmark section
Correct formatting of the benchmark table in ChangeLog.
2025-09-17 09:08:01 -07:00
Victor Zverovich
09005428ab
Fix formatting in ChangeLog.md 2025-09-17 09:05:36 -07:00
Victor Zverovich
aaefc029d1 Update changelog 2025-09-17 09:05:15 -07:00
Victor Zverovich
9fb9f17dac Update changelog 2025-09-17 09:04:32 -07:00
Victor Zverovich
a3b3d7ed46 Simplify duration cast 2025-09-13 11:32:03 -07:00
Victor Zverovich
9aee518f3e Update changelog 2025-09-13 10:51:11 -07:00
Victor Zverovich
83189189a1 Remove get_dynamic_spec 2025-09-13 10:38:50 -07:00
Victor Zverovich
9bb14ffc47 Remove deprecated APIs 2025-09-13 10:07:16 -07:00
Victor Zverovich
556c4177b6 Remove deprecated localtime 2025-09-13 09:41:24 -07:00
Victor Zverovich
bfdef8b15d Remove deprecated functions 2025-09-13 09:25:53 -07:00
Victor Zverovich
e33c76a1c2 Update changelog 2025-09-13 08:17:13 -07:00
Victor Zverovich
f9face7147 Update changelog 2025-09-13 07:59:25 -07:00
Uilian Ries
36390db094
Add Conan instructions in getting-started page (#4537)
Signed-off-by: Uilian Ries <uilianries@gmail.com>
2025-09-10 11:04:48 -07:00
crueter
da97ba2914
[cmake] fix MASTER_PROJECT heuristic; only enable install if master project (#4536)
SOURCE vs CURRENT_SOURCE is unreliable. `DEFINED PROJECT_NAME` is a much more portable way of determination.

Also, installation shouldn't default to on if it is a subproject.
2025-09-10 11:01:25 -07:00
Victor Zverovich
79a5e93027 Update changelog 2025-09-10 10:59:23 -07:00
Fatih BAKIR
18e160eb4c
Add missing include for crtdbg.h (#4534)
This header uses _CRT_ASSERT, which is defined in crtdbg.h but does not
include it, causing a build error in format-test.cc

Fixes the issue by including the header
2025-09-08 09:37:43 -07:00
Victor Zverovich
53d006abfd Update changelog 2025-09-07 09:20:53 -07:00
Victor Zverovich
63bef33999 Update changelog 2025-09-07 08:36:07 -07:00
Victor Zverovich
d7cbfef11c Update changelog 2025-09-07 08:29:03 -07:00
Victor Zverovich
26b033e239 Update changelog 2025-09-07 07:54:59 -07:00
Victor Zverovich
609188cb92 Cleanup macro definitions 2025-09-07 07:44:49 -07:00
Victor Zverovich
84b9c00711 Update changelog 2025-09-07 07:44:11 -07:00
Marcel
9908d00037
Delete Bazel support (#4530) 2025-09-05 15:53:13 -07:00
Anas
0a94e06750
Combined emphases into a single escape sequence (#4528) 2025-09-05 09:50:56 -07:00
Victor Zverovich
053d262944 Add .clang-tidy 2025-09-03 11:05:49 -07:00
Victor Zverovich
e72e43af1c Apply clang-tidy 2025-09-03 11:05:41 -07:00
Victor Chernyakin
80549a630e
Avoid repeated call to GetLastError in file::size (#4522) 2025-09-03 08:38:31 -07:00
dependabot[bot]
f17b9aa44c
Bump actions/checkout from 4.2.0 to 5.0.0 (#4523)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4.2.0 to 5.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](d632683dd7...08c6903cd8)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 5.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-02 09:04:42 -07:00
Victor Zverovich
882702c219 Apply clang-tidy 2025-09-01 12:21:17 -07:00
Victor Zverovich
16d371b649 Apply clang-tidy 2025-09-01 12:00:35 -07:00
Victor Zverovich
619b3a5aa0 Suppress a false positive in clang-tidy 2025-09-01 11:42:00 -07:00
Victor Zverovich
79c7f8a70b Apply clang-tidy 2025-09-01 10:49:49 -07:00
Victor Zverovich
20e0d6d8dd Update docs 2025-09-01 10:15:54 -07:00
Victor Zverovich
8ba99c0f05 Update docs 2025-09-01 10:04:54 -07:00
Victor Zverovich
656228fbee Update docs 2025-09-01 09:50:45 -07:00
Victor Zverovich
02fb76d69b Add FMT_STATIC_FORMAT 2025-09-01 09:15:32 -07:00
Victor Zverovich
a57f196cad Apply coding conventions 2025-09-01 07:24:08 -07:00
Victor Zverovich
a75e8af487 Update docs 2025-08-31 10:51:29 -07:00
Victor Zverovich
e129591f02 Update changelog 2025-08-31 10:24:40 -07:00
Victor Zverovich
13d4f8469e Remove deprecated vformat_to 2025-08-31 10:02:36 -07:00
Victor Zverovich
e2f89e6d21 Remove deprecated aliases 2025-08-31 09:40:11 -07:00
Victor Zverovich
489fd7ca4b Simplify locale handling 2025-08-31 09:33:43 -07:00
Victor Zverovich
69324379f2 Mark detail::assert_fail as deprecated 2025-08-31 08:11:57 -07:00
Björn Schäpers
ff1caa58c9
Use FMT_THROW in report_error (#4521)
With exceptions: No change (if FMT_THROW is not user provided)

Without exceptions: Now also states where it comes from (report_error)
and uses fprintf instead of fputs.

My motivation is, now that I have my own fmt::assert_failed, also to get
rid of fputs for my embedded build.
2025-08-31 08:05:54 -07:00
Björn Schäpers
e181e94140
Add FMT_CUSTOM_ASSERT_FAIL (#4505)
That way one can provide ones own implementation for assert_fail, which
is moved out of the detail namespace. For binary compatibility the
detail version stays to call the outer version.
2025-08-25 09:02:00 -07:00
Victor Zverovich
43c88d00ae Update changelog 2025-08-24 10:08:53 -07:00
Victor Zverovich
33a7d55c9b Update changelog 2025-08-24 10:04:00 -07:00
Victor Zverovich
8e4676e4a5 Remove deprecated template parameter 2025-08-24 09:52:17 -07:00
Victor Zverovich
b5c4d25cd1 Rename is_char to is_code_unit 2025-08-24 09:30:50 -07:00
Victor Zverovich
a77efa4b4a Move is_*char to detail 2025-08-24 09:16:18 -07:00
Victor Zverovich
e7e71009c7 Make FP formatting compatible with exotic chars 2025-08-24 08:49:54 -07:00
Victor Zverovich
b7b261977e Simplify report_error 2025-08-24 08:30:17 -07:00
Victor Zverovich
61e0503daf Merge workarounds for bogus MSVC warnings 2025-08-24 08:18:47 -07:00
Victor Zverovich
72c82296d6 Put FMT_END_* together 2025-08-17 12:25:40 -07:00
Victor Zverovich
a402614e8c Remove deprecated aliases 2025-08-17 11:52:00 -07:00
Victor Zverovich
e719c43cc6 Apply coding conventions 2025-08-17 10:59:16 -07:00
Victor Zverovich
8a8ff6177c Remove deprecated and undocumented has_formatter 2025-08-17 10:52:45 -07:00
Victor Zverovich
5b99b334f0 Remove redundant else 2025-08-17 10:03:01 -07:00
Mattias Ljungström
9588458917
Silence unreachable code warnings in MSVC when FMT_USE_EXCEPTIONS are disabled. (#4515) 2025-08-17 09:35:28 -07:00
Victor Zverovich
f91dc80f4c Minor cleanup 2025-08-16 14:49:26 -07:00
Victor Zverovich
4120581167 Minor cleanup 2025-08-16 14:20:06 -07:00
Victor Zverovich
7ffc3ca15b Minor cleanup 2025-08-16 12:20:02 -07:00
Victor Zverovich
e9ddc97b9a Remove unnecesary constexpr and inline more 2025-08-10 08:40:35 -07:00
Murat Toprak
0d145936ec
Handle allocator propagation in basic_memory_buffer::move, Fix #4487 (#4490)
* Handle allocator propagation in basic_memory_buffer::move

Update `basic_memory_buffer::move` to respect `propagate_on_container_move_assignment`allocator trait.
If the allocator should not propagate and differs from the target's allocator,
fallback to copying the buffer instead of transferring ownership.

This avoids potential allocator mismatch issues and ensures exception safety.

* Add test cases for the updated move ctor

- Added two test cases `move_ctor_inline_buffer_non_propagating` and `move_ctor_dynamic_buffer_non_propagating`
- Added `PropageteOnMove` template parameter to `allocator_ref` class to be compatible with the old test cases
- `allocator_ref` now implements `!=` and `==` operators
2025-08-09 08:14:15 -07:00
Victor Chernyakin
8f3a965186
Refactor ansi_color_escape to track size instead of using a null terminator (#4511) 2025-08-09 08:08:41 -07:00
Victor Chernyakin
ae8cb1e8e6
Ignore some more files in .gitignore (#4512) 2025-08-05 13:41:50 -07:00
Victor Zverovich
add164f6b3 Optimize the default FP formatting 2025-08-03 12:46:38 -07:00
Victor Zverovich
23059d558e Fix exponent size computation 2025-08-03 10:00:24 -07:00
Victor Zverovich
505cc3d0c2 Simplify remove_trailing_zeros 2025-08-03 08:45:48 -07:00
Victor Zverovich
b8a502615d Revert "Optimize the default FP formatting"
This reverts commit 93f03953af6b0268e1a29bb5b23d50f72b87a151.
2025-08-03 07:40:13 -07:00
dependabot[bot]
814f51eab6
Bump github/codeql-action from 3.28.16 to 3.29.5 (#4510)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.16 to 3.29.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](28deaeda66...51f77329af)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.29.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-01 11:52:42 -07:00
Victor Zverovich
93f03953af Optimize the default FP formatting 2025-07-27 08:56:07 -07:00
Dominic Pöschko
35dcc58263
fix buffer overflow on all emphasis flags set (#4498) 2025-07-20 10:43:33 -07:00
Catherine
553ec11ec0
Make compatible with WASI (#4497)
WASI is a POSIX subset that doesn't have `dup`, `dup2`, or `pipe` system calls.

Fixes #4496.
2025-07-14 15:51:04 -07:00
autoantwort
a0ecfe3e1c
msvc + ninja + modules: Fix build when consuming fmtlib while using a Ninja generator (#4495)
Co-authored-by: Leander Schulten <Leander.Schulten@tetys.de>
2025-07-13 10:28:00 -07:00
Victor Zverovich
127413ddaa
Add OpenSSF Best Practices badge 2025-07-12 07:12:18 -07:00
Victor Zverovich
7d29ebe4af Minor cleanup 2025-07-12 07:09:38 -07:00
anonymous
20c8fdad06
Fix import std in clang++ (#4488)
fix module-compilation error when defined `FMT_IMPORT_STD`
2025-07-07 11:47:48 -07:00
Victor Zverovich
300ce75ca6 Handle invalid glibc FILE buffer 2025-07-06 11:17:16 -07:00
Victor Zverovich
513f978241 Cleanup os-test 2025-07-04 10:52:59 -07:00
Victor Zverovich
6a3b40524c Use actual example code and move safe_fopen to os-test 2025-07-04 09:50:55 -07:00
Victor Chernyakin
2fa3e1a1bb
Fix interaction between debug presentation, precision, and width for strings (#4478) 2025-07-04 07:33:47 -07:00
dependabot[bot]
fc8d07cfe5
Bump msys2/setup-msys2 from 2.27.0 to 2.28.0 (#4485)
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.27.0 to 2.28.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](61f9e5e925...40677d36a5)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-version: 2.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 08:42:01 -07:00
Miuna
27c5aab349
Fix unwanted char promotion in decimal writer with wchar_t (#4483) 2025-07-01 07:03:57 -07:00
Victor Zverovich
bc0193535a Update image 2025-06-29 07:50:09 -07:00
Miuna
353bd895a2
Add FMT_EXPORT on ranges.h customization points (#4476) 2025-06-24 10:30:44 -07:00
Tomek-Stolarczyk
953cffa701
Replace memset with constexpr fill_n in bigint::align (#4471)
Use fill_n in place of memset in bigint::align to respect constexpr.
2025-06-23 16:18:16 -07:00
Vladislav Shchapov
571c02d475
Add xchar support for std::byte formatter (#4480)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-06-23 14:02:11 -07:00
Remy Jette
f4345467fc
Fix compilation on clang-21 / libc++-21 (#4477)
`<cstdlib>` was not being included, so malloc and free were only declared
via transitive includes. Some includes changed in the latest libc++-21
build which broke fmt.

Also changed `malloc`/`free` to `std::malloc` and `std::free`, as
putting those symbols in the global namespace is optional for the
implementation when including `<cstdlib>`.
2025-06-21 07:28:14 -07:00
Victor Chernyakin
1ef8348070
Properly constrain detail::copy optimization (#4474) 2025-06-21 06:59:35 -07:00
Sahil Sinha
a5dccffa56 Add double and float support to scan test
- Add double_type and float_type to scan_type enum
- Add double* and float* pointers to scan_arg union
- Add constructors for double and float scan arguments
- Add switch cases for double and float types in visit()
- Implement basic read() functions for floating-point parsing

This partially resolves the TODO comment 'more types' in scan.h by adding
support for the two most commonly needed floating-point types.
2025-06-21 06:57:20 -07:00
Victor Zverovich
4a149f513f Test non-SSO constexpr string formatting 2025-06-20 07:10:12 -07:00
Victor Chernyakin
067bc479b4
Avoid redundant work when processing UTF-8 strings (#4475) 2025-06-20 06:39:06 -07:00
Victor Zverovich
730fd4d9a7 Remove redundant tests 2025-06-08 08:46:22 -07:00
Mikhail Svetkin
5860688d7e
Enable constexpr support for fmt::format (fmtlib#3403) (#4456) 2025-06-07 07:16:49 -07:00
Victor Zverovich
46be88bc1e Cleanup FP formatting 2025-06-01 09:15:20 -07:00
Thomas Khyn
cc88914904
Export fmt::dynamic_format_arg_store in fmt module (#4459) 2025-06-01 08:50:14 -07:00
Victor Zverovich
fc0c76a075 Handle large precision 2025-06-01 08:49:41 -07:00
dependabot[bot]
6332a38529
Bump ossf/scorecard-action from 2.4.0 to 2.4.2 (#4462)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.2.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](62b2cac7ed...05b42c6244)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-01 08:48:36 -07:00
Victor Zverovich
02de29e003 Remove a reference to a compromised account 2025-05-30 18:24:56 -07:00
Victor Zverovich
6d51c78c1e Cleanup FP formatting 2025-05-30 16:45:21 -07:00
Victor Zverovich
0f4e9d0bde Cleanup FP formatting 2025-05-30 16:05:57 -07:00
Victor Zverovich
d9d50495ac Optimize the default FP formatting 2025-05-30 13:45:04 -07:00
Victor Zverovich
befbc5fdb8 Fix ADL lookup for memory_buffer 2025-05-26 09:44:35 -07:00
Victor Zverovich
8aa1d6a9fb Minor cleanup 2025-05-25 10:14:24 -07:00
Nikhil
6d79757a38
Interpret precision as display width (#4443) 2025-05-25 08:42:47 -07:00
Victor Zverovich
1ff0b7f5e1 Cleanup warning suppression 2025-05-24 09:37:01 -07:00
Edoardo Morandi
ea985e84f8
Remove some implicit conversions (#4447)
* fix: avoid an implicit cast

The "1" used for the bitshift is treated as int, and this causes an
implicit conversion to `UInt` when performing the logical and.
Explicitly casting the number to `UInt` avoids the warning.

* fix: avoid implicit conversions for indices

Some indices in `include/fmt/base.h` are expressed as `int` types, which
causes an implicit conversion to a `size_t` when they are actually used
as index. Explicitly casting the value avoids the warning.

* fix: avoid an implicit conversion using size_t

The number of bits is used to express the size of a buffer. Using an
`int` causes an implicit conversion warning, let's use a `size_t` which
is the right type for the job.
2025-05-24 09:22:03 -07:00
Andreas Reischuck
f7033da09e
Avoid include locale inline if C++20 modules are enabled (#4451)
MSVC hints with:
```
fmt\include\fmt\format-inl.h(26): warning C5244: '#include <locale>' in the purview of module 'fmt' appears erroneous.  Consider moving that directive before the module declaration, or replace the textual inclusion with 'import <locale>;'.
```

Then fails the build with `type redefinition`.
2025-05-21 17:18:18 -07:00
Tobias Schlüter
b723c021df
Give useful error when misusing fmt::ptr. (#4453)
static_assert(bla, "") prints an empty message but not the condition with at least MSVC. Add an informative message.
2025-05-20 12:21:06 -07:00
Victor Zverovich
3ba3c390fb Clarify that formatting of pointers is disallowed 2025-05-17 10:16:58 -07:00
Kefu Chai
ab161a71c6
Fix some typos in comments (#4448)
- s/instantion/instantiation/
- s/uninitalized/uninitialized/
- s/costexpr/constexpr/

Signed-off-by: Kefu Chai <tchaikov@gmail.com>
2025-05-15 06:28:14 -07:00
Victor Chernyakin
b5266fd3b9
Remove some redundant consts (#4445)
`constexpr` variables are implicitly `const`.
2025-05-12 10:41:58 -07:00
Victor Zverovich
9b0ebd4435 Cleanup base-test 2025-05-11 15:42:08 -07:00
Victor Zverovich
7af94e5597 Remove old gcc workaround 2025-05-11 12:35:28 -07:00
Victor Zverovich
2924fcf8f6 Cleanup base-test 2025-05-11 10:36:42 -07:00
Victor Zverovich
102752ad45 Update docs 2025-05-11 09:13:12 -07:00
Victor Zverovich
a6cd72c9eb Cleanup base-test 2025-05-11 09:01:28 -07:00
Victor Zverovich
07885271a1 Minor cleanup 2025-05-11 07:54:23 -07:00
Jeremy Rifkin
4999416e5c
Fix reference_wrapper ambiguity with format_as (#4434) 2025-05-10 11:15:45 -07:00
n-stein
55a8f6a4be
Change component prefix for NSIS compatibility (#4442) 2025-05-09 12:09:18 -07:00
Victor Zverovich
eb9a95d426 Clarify that formatting of pointers is disallowed 2025-05-05 10:55:59 -07:00
Victor Zverovich
d5c33e4f45 Make template parameter order consistent 2025-05-04 15:23:48 -07:00
Victor Zverovich
a2225f2887 Remove unused include 2025-05-04 15:16:38 -07:00
Victor Zverovich
b43b2f9537 Cleanup standard formatters 2025-05-04 13:04:06 -07:00
Victor Zverovich
1312b4a162 Cleanup standard formatters 2025-05-04 12:37:28 -07:00
Victor Zverovich
4404dc05dd Consolidate implementation details 2025-05-04 10:48:47 -07:00
Victor Zverovich
7bb6fcb325 Bump version 2025-05-04 09:36:07 -07:00
Victor Zverovich
59259a5fde Make a doc directory if it doesn't exist 2025-05-03 10:29:35 -07:00
Victor Zverovich
542ea7c40b Clarify that Formatter parameter is deprecated 2025-05-03 10:28:46 -07:00
Victor Zverovich
40626af88b Update version 2025-05-03 09:36:27 -07:00
Victor Zverovich
7fdd6846ba Bump version 2025-05-03 09:02:35 -07:00
Victor Zverovich
6caff7ed9c Cleanup test 2025-05-03 08:36:42 -07:00
Victor Zverovich
71a5483875 Update changelog 2025-05-03 07:29:31 -07:00
Victor Zverovich
448929d491 Update and apply clang-format 2025-05-03 07:29:31 -07:00
dependabot[bot]
26d87edab1
Bump github/codeql-action from 3.28.13 to 3.28.16 (#4432)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.13 to 3.28.16.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](1b549b9259...28deaeda66)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-03 06:59:36 -07:00
Victor Zverovich
505ee058f7 Update changelog 2025-04-27 11:57:40 -07:00
Victor Zverovich
ccab417195 Update changelog 2025-04-27 11:57:15 -07:00
Victor Zverovich
ec1349d348 Update changelog 2025-04-27 11:56:07 -07:00
Victor Zverovich
0ed2a65a8a Clarify why we use __builtin_strlen instead of strlen 2025-04-27 11:08:27 -07:00
Victor Zverovich
e22c943070 Update changelog 2025-04-27 10:59:31 -07:00
Victor Zverovich
b252bad3c6 Update changelog 2025-04-27 10:29:56 -07:00
Victor Zverovich
2680831231 Cleanun string_view 2025-04-27 09:34:38 -07:00
Barry Revzin
8978ab09b2
Avoiding __builtin_strlen (#4429) 2025-04-27 09:32:10 -07:00
Victor Zverovich
c936e2e44e Implement debug format for error_code 2025-04-27 09:06:17 -07:00
Victor Zverovich
a7d7b894cd Implement the s specifier for error_code 2025-04-27 08:51:18 -07:00
Victor Zverovich
e98155a6fb Remove redundant specializations 2025-04-26 10:41:03 -07:00
Victor Zverovich
41b3bed4d2 Clarify why we don't use qualified names 2025-04-26 10:12:06 -07:00
Victor Zverovich
67d9e49322 Update changelog 2025-04-26 09:15:47 -07:00
Victor Zverovich
9db5e4df22 Don't specialize std::is_floating_point 2025-04-26 08:17:05 -07:00
Victor Zverovich
906eaf2ddb Make specifier order consistent 2025-04-25 12:10:21 -07:00
Victor Zverovich
9f6c12c3dc Remove deprecated localtime from docs 2025-04-25 11:58:58 -07:00
rlalik
2d0518b5f7
Fix cmake error in pedantic mode (#4426)
Suppresses error:
CMake Error (dev) at build/coverage/_deps/fmt-src/CMakeLists.txt:208 (set):
  uninitialized variable 'CMAKE_MODULE_PATH'
This error is for project developers. Use -Wno-error=dev to suppress it.
2025-04-24 11:41:33 -07:00
Victor Zverovich
c81cbed2b7 Simplify test 2025-04-23 12:32:26 -07:00
krzysztofkortas
c7925241c7
Remove core.h from README (#4422) 2025-04-21 11:27:40 -07:00
LocalSpook
c709138359 Add support for incomplete types 2025-04-20 10:16:11 -07:00
krzysztofkortas
db405954cd
Remove fmt/core.h from docs (#4421) 2025-04-20 07:40:18 -07:00
Victor Zverovich
0a917ee2f5 Minor comment tweak 2025-04-19 10:18:01 -07:00
Victor Zverovich
969d4aef60 Update doc image 2025-04-19 10:08:24 -07:00
Victor Zverovich
8061c7c8c4 Cleanup duration formatter 2025-04-19 10:08:24 -07:00
Victor Zverovich
7b59df4119 Remove redundant member 2025-04-19 10:08:23 -07:00
hirohira
b8192d233a
Fix build error with MSVC v141 (#4413) 2025-04-15 08:21:38 -07:00
Victor Zverovich
e814b5fabf Reduce template parametrization 2025-04-13 10:17:17 -07:00
Victor Zverovich
ed0d216f7e Fix localization and formatting of timezone names 2025-04-13 09:52:59 -07:00
Victor Zverovich
bd9554a29e Fix formatting of timezone names 2025-04-13 08:52:26 -07:00
Victor Zverovich
f086dc0d27 Fix timezone handling in tm 2025-04-13 08:23:40 -07:00
Victor Zverovich
f10b6dd816 Improve chrono formatting 2025-04-12 09:59:06 -07:00
Victor Zverovich
f470b9c566 Cleanup chrono tests and set consistent TZ 2025-04-12 09:12:49 -07:00
Victor Zverovich
b28214487d Fix handling of %Z 2025-04-12 08:51:22 -07:00
Victor Zverovich
6d69f0c5f2 Improve chorno tests 2025-04-12 08:24:49 -07:00
Victor Zverovich
da776c9a66 Test timezone 2025-04-12 07:53:58 -07:00
Mattes D
64db979e38
Added a missing FMT_STRING in fmt::println() (#4407) 2025-04-07 15:35:55 -07:00
Victor Zverovich
5f2e61fdd5 Cleanup chrono detail 2025-04-06 09:54:47 -07:00
Victor Zverovich
b3d45e1d3f Remove fmt_detail 2025-04-06 09:24:20 -07:00
dependabot[bot]
5f6fb96df1
Bump github/codeql-action from 3.28.8 to 3.28.13 (#4403)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.8 to 3.28.13.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](dd746615b3...1b549b9259)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-04-01 13:29:20 -07:00
Victor Zverovich
5199e0f885 Fix a flush issue on libstdc++ 2025-03-30 10:54:14 -07:00
Victor Zverovich
2f58430573 Move buffering tests to os-test 2025-03-30 09:38:40 -07:00
Vertexwahn
d5d32c1e81
Bazel support: Update platforms to 0.0.11 (#4400) 2025-03-30 08:36:25 -07:00
Victor Zverovich
204661287b Improve local_time test 2025-03-29 10:56:11 -07:00
Victor Zverovich
e1ab383361 Report an error when timezone is not available 2025-03-29 09:10:00 -07:00
Victor Zverovich
b9e0e94a01 Enable more chrono tests on Windows 2025-03-29 08:20:49 -07:00
Victor Zverovich
a81842428d Update changelog 2025-03-29 08:14:25 -07:00
Victor Zverovich
f53055efe0 Revert "Workaround an ABI issue in spdlog"
This reverts commit 784eac839df77df1852dc1c50b17b76c3aec8f4a.
2025-03-29 07:48:20 -07:00
Victor Zverovich
b2dfcb2b80 Fix local_time test 2025-03-23 19:51:33 -07:00
Victor Zverovich
7ac97cbd1d Enable some local_time tests and make them deterministic 2025-03-23 11:50:03 -07:00
Victor Zverovich
17898794a9 Use fmt::local_time 2025-03-23 11:18:14 -07:00
Victor Zverovich
443a8ef342 Deprecate fmt::localtime 2025-03-23 10:46:02 -07:00
Victor Zverovich
3607e92dc9 Bump version 2025-03-23 10:44:46 -07:00
Victor Zverovich
43e31614cc Test ambiguous time 2025-03-23 10:34:37 -07:00
Victor Zverovich
989826ce50 Update changelog 2025-03-22 08:03:54 -07:00
Victor Zverovich
9d6e24c64e Fix handling of long with FMT_BUILTIN_TYPES=0 2025-03-22 07:52:54 -07:00
Victor Zverovich
0843317e08 Update changelog 2025-03-22 07:25:46 -07:00
Victor Zverovich
784eac839d Workaround an ABI issue in spdlog 2025-03-22 07:01:45 -07:00
Victor Zverovich
6fdf225a32 Always inline value ctors in optimized gcc mode only
This reverts commit 332da79bf37d06bb549810fd9420fd47407ebd89.
2025-03-16 10:03:53 -07:00
Victor Zverovich
332da79bf3 Always inline value ctors 2025-03-16 09:31:52 -07:00
Victor Zverovich
7b273fbb54 Minor cleanup 2025-03-16 08:58:44 -07:00
Victor Zverovich
191c504b10 Cleanup build config 2025-03-16 08:24:31 -07:00
Victor Zverovich
d13fb6092f Cleanup build config 2025-03-15 12:45:36 -07:00
Victor Zverovich
dd780fde44 Add clang-3.4 2025-03-15 12:22:10 -07:00
Dean Glazeski
37e6474718
Fix dynamic named arg format spec handling (#4361)
When dealing with dynamic named format args, need to account for
nested named args when skipping the content of the replacement.

Fixes #4360
2025-03-15 09:34:11 -07:00
Victor Zverovich
77c0fc07d9 Switch to supported ubuntu image 2025-03-09 17:43:29 -07:00
Victor Zverovich
9212ff6ca1 Apply coding conventions and use constexpr 2025-03-02 09:03:06 -08:00
Dean Glazeski
864bdf9638
Report error on duplicate named arg names (#4367) 2025-03-02 07:47:03 -08:00
Victor Chernyakin
b776cf66fc
Optimize text_style using bit packing (#4363) 2025-03-01 11:18:19 -08:00
dependabot[bot]
bdbf957b9a Bump msys2/setup-msys2 from 2.25.0 to 2.27.0
Bumps [msys2/setup-msys2](https://github.com/msys2/setup-msys2) from 2.25.0 to 2.27.0.
- [Release notes](https://github.com/msys2/setup-msys2/releases)
- [Changelog](https://github.com/msys2/setup-msys2/blob/main/CHANGELOG.md)
- [Commits](c52d1fa9c7...61f9e5e925)

---
updated-dependencies:
- dependency-name: msys2/setup-msys2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-01 07:56:45 -08:00
Thomas Khyn
577fd3be88 Fix TU-local entity exposition error in GCC 15 2025-02-26 11:14:15 -08:00
Thomas Khyn
faac8b1fa9 Remove exports in std.h
This fixes 'declaration of partial specialization in unbraced export-declaration' errors in GCC 15
2025-02-26 11:14:15 -08:00
Victor Zverovich
123913715a Update version 2025-02-26 10:14:28 -08:00
Victor Zverovich
8c1059b92e Update changelog 2025-02-25 17:32:17 -08:00
Victor Zverovich
4e5aafbf43 Bump version 2025-02-25 17:20:47 -08:00
Victor Zverovich
db30fb3b81 Update changelog 2025-02-25 17:20:29 -08:00
Victor Zverovich
3401ce2be2 Fix ABI compatibility 2025-02-25 13:32:24 -08:00
Dean
7f7695524a
Fix conflict with std::ignore (#4356)
In situations where `using namespace std;` is used, compiler warnings
can be generated because of local variables named `ignore`. This renames
those variables to something else to address the name conflict.
2025-02-19 23:08:21 -08:00
Dean
251320fcb7
Add .vs folder to .gitignore (#4355)
Opening the fmt folder as a CMake project with Visual Studio creates
this directory. The contents can be ignored.
2025-02-19 22:44:31 -08:00
Victor Chernyakin
94ab51cb8c
Simplify implementation of operator""_cf (#4349) 2025-02-14 02:02:09 -08:00
Victor Zverovich
0ca42e836e Workaround an MSVC v140 bug 2025-02-13 14:12:16 +01:00
Thomas Khyn
ed27df5760
Replace forward slashes by backslashes in BMI path for MSVC. (#4344)
* Fix slashes in BMI path for MSVC builds
* Fix BMI path for MSVC builds when building with Ninja generator
2025-02-10 00:18:33 -08:00
Victor Zverovich
d42a068dbd Apply coding conventions 2025-02-05 11:06:45 -08:00
Markus Kitsinger
f2cec917da
Move is_compiled_string to public API (#4342) 2025-02-04 16:20:27 -08:00
Sergiu Deitsch
d5b866e242
fix gcc 8.3 compile errors (#4336) 2025-02-03 08:51:58 -08:00
dependabot[bot]
5676e408f5
Bump github/codeql-action from 3.27.0 to 3.28.8 (#4337)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.27.0 to 3.28.8.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](662472033e...dd746615b3)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-01 12:08:34 -08:00
dependabot[bot]
71d24b564d
Bump actions/upload-artifact from 4.4.0 to 4.6.0 (#4339)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4.4.0 to 4.6.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](50769540e7...65c4c4a1dd)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-02-01 11:30:13 -08:00
LocalSpook
c9267da4df Fix typo in FMT_HAS_BUILTIN check 2025-01-31 08:42:19 -08:00
Victor Zverovich
373855c1b0 Clarify difference in FP representation 2025-01-26 13:49:54 -08:00
Victor Zverovich
52eeeb52a6 Make exponent threshold depend on representation (#3649) 2025-01-26 12:10:48 -08:00
Victor Zverovich
9cf9f38ede Update version 2025-01-25 10:08:57 -08:00
Victor Zverovich
4946bdb729 Update changelog 2025-01-25 08:53:02 -08:00
Rafał Lalik
01a5b56f0d Fix error of unitialized variable FMT_HEADERS
This happens when using e.g. pedantic mode in cmake-init.
2025-01-25 08:41:49 -08:00
timsong-cpp
cb6fdf2191
Restore constraint on map formatter (#4326)
* Restore constraint on map formatter
* Remove unnecessary double parens
2025-01-25 08:31:07 -08:00
timsong-cpp
f841ae61e2
Fix #4303: avoid instantiating formatter<const T> (#4325) 2025-01-24 10:53:10 -08:00
Matt
a3d05d70ce
Silence a constexpr warning when compiling with MSVC and /W4 (#4322) 2025-01-23 12:11:23 -08:00
Victor Zverovich
41539c29f3 Workaround a bug in gcc 6 (#4318) 2025-01-22 11:12:41 -08:00
Victor Zverovich
aabe63910c Tweak changelog 2025-01-20 09:33:33 -08:00
Victor Zverovich
f90090be2c Update changelog 2025-01-19 10:00:40 -08:00
Victor Zverovich
9ff9c695db Bump version 2025-01-18 10:28:46 -08:00
Victor Zverovich
06ad1224eb Update changelog 2025-01-18 10:05:40 -08:00
Victor Zverovich
5f0572acdc Workaround a compilation error on gcc 9.4 2025-01-18 09:01:00 -08:00
Vladislav Shchapov
898d438571
Fix formatting into std::ostreambuf_iterator using a compiled format (#4312)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-18 07:16:06 -08:00
Aaron Bishop
937b7c5c10
Add args() accessor back to fmt::format_context (#4310)
Add args() accessor back to fmt::format_context
Add test that would fail to compile if you can't create a fmt::format_context from another fmt::format_context
2025-01-17 10:28:34 -08:00
Victor Zverovich
01914f0389 Reduce size of basic_specs 2025-01-12 09:18:11 -08:00
Victor Zverovich
c43da35701 Workaround an ICE when using modules with gcc 14.2 and earlier 2025-01-12 08:57:43 -08:00
Victor Zverovich
8303d140a1 Update version 2025-01-12 08:15:44 -08:00
Victor Zverovich
b0b3dc5ff9 Bump version 2025-01-12 08:13:56 -08:00
Victor Zverovich
586ea06f02 Rename set_fill to copy_fill_from 2025-01-11 09:22:15 -08:00
Victor Zverovich
5750f434fa Update changelog 2025-01-11 09:06:00 -08:00
Marcel Breyer
bfbdc2be9a Add parameter to the fallback to_sys function. 2025-01-11 08:48:20 -08:00
Victor Zverovich
87e0072673 Update changelog 2025-01-11 07:21:28 -08:00
Victor Zverovich
d57040f949 Prefix components 2025-01-10 19:51:55 -08:00
Victor Zverovich
21aa0956d4 Restore ABI compatibility 2025-01-10 17:36:09 -08:00
Edoardo Lolletti
3f864a4505
Address MSVC C4127 warning when formatting non unicode tm (#4299)
Use `const_check` to silence visual studio's W4 level diagnostic regarding conditional expressions being constants, addresses https://github.com/fmtlib/fmt/issues/4294
2025-01-08 14:21:48 -08:00
Trim21
093b39ca5e
Update docs for meson (#4291) 2025-01-06 10:52:57 -08:00
Vladislav Shchapov
2c3a5698ef Simplify a copying the fill from basic_specs
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-05 09:33:05 -08:00
Victor Zverovich
fc1b0f3486 Clarify use of FMT_THROW in a comment 2025-01-05 07:31:19 -10:00
GamesTrap
1d066890c7 Resolve C4702 unreachable code warnings 2025-01-03 16:39:11 -08:00
Vladislav Shchapov
dad3237514 Fix a bug when copying the fill from basic_specs
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-03 09:16:21 -08:00
Vladislav Shchapov
880e1494dc Improve xchar support for std::bitset formatter
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-01-03 09:16:21 -08:00
64 changed files with 4178 additions and 3158 deletions

View File

@ -7,6 +7,7 @@ IndentCaseLabels: false
AlwaysBreakTemplateDeclarations: false
DerivePointerAlignment: false
AllowShortCaseLabelsOnASingleLine: true
QualifierAlignment: Left
AlignConsecutiveShortCaseStatements:
Enabled: true
AcrossEmptyLines: true

4
.clang-tidy Normal file
View File

@ -0,0 +1,4 @@
Checks: modernize-use-trailing-return-type
CheckOptions:
- key: modernize-use-trailing-return-type.TransformLambdas
value: none

View File

@ -25,7 +25,7 @@ jobs:
language: c++
- name: Upload crash
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts

View File

@ -7,11 +7,10 @@ permissions:
jobs:
build:
# Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken.
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Add Ubuntu mirrors
run: |
@ -25,7 +24,7 @@ jobs:
run: |
sudo apt update
sudo apt install doxygen
pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1
pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1
cmake -E make_directory ${{runner.workspace}}/build
# Workaround https://github.com/actions/checkout/issues/13:
git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)"

View File

@ -13,16 +13,16 @@ jobs:
format_code:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install clang-format
run: |
wget https://apt.llvm.org/llvm.sh
sudo bash ./llvm.sh 17
sudo apt install clang-format-17
sudo bash ./llvm.sh 21
sudo apt install clang-format-21
- name: Run clang-format
run: |
find include src -name '*.h' -o -name '*.cc' | \
xargs clang-format-17 -i -style=file -fallback-style=none
xargs clang-format-21 -i -style=file -fallback-style=none
git diff --exit-code

View File

@ -7,87 +7,143 @@ permissions:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
strategy:
matrix:
cxx: [g++-4.9, g++-10, clang++-9]
cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11]
build_type: [Debug, Release]
std: [11]
shared: [""]
include:
- cxx: g++-4.9
install: sudo apt install g++-4.9
- cxx: g++-8
- cxx: clang++-3.6
- cxx: g++-11
build_type: Debug
std: 14
install: sudo apt install g++-8
- cxx: g++-8
build_type: Debug
std: 17
install: sudo apt install g++-8
- cxx: g++-9
build_type: Debug
std: 17
- cxx: g++-10
install: sudo apt install g++-11
- cxx: g++-11
build_type: Debug
std: 17
- cxx: g++-11
build_type: Debug
std: 20
install: sudo apt install g++-11
- cxx: clang++-8
build_type: Debug
std: 17
cxxflags: -stdlib=libc++
install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev
- cxx: clang++-9
install: sudo apt install clang-9
- cxx: clang++-9
build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17
install: sudo apt install clang-9
- cxx: clang++-11
build_type: Debug
std: 20
- cxx: clang++-11
build_type: Debug
std: 20
cxxflags: -stdlib=libc++
install: sudo apt install libc++-11-dev libc++abi-11-dev
- cxx: g++-13
build_type: Release
std: 23
install: sudo apt install g++-13
shared: -DBUILD_SHARED_LIBS=ON
- cxx: clang++-11
build_type: Debug
std: 17
cxxflags: -stdlib=libc++
install: sudo apt install clang-11 libc++-11-dev libc++abi-11-dev
- cxx: clang++-11
install: sudo apt install clang-11
- cxx: clang++-11
build_type: Debug
fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON
std: 17
install: sudo apt install clang-11
- cxx: clang++-14
build_type: Debug
std: 20
- cxx: clang++-14
build_type: Debug
std: 20
cxxflags: -stdlib=libc++
install: sudo apt install libc++-14-dev libc++abi-14-dev
steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'
run: sudo timedatectl set-timezone 'Europe/Kyiv'
- name: Add repositories for older GCC
- name: Install GCC 4.9
run: |
# Below repo provides GCC 4.9.
sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial main'
sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe'
sudo apt update
sudo apt install libatomic1 libc6-dev libgomp1 libitm1 libmpc3
# https://launchpad.net/ubuntu/xenial/amd64/g++-4.9/4.9.3-13ubuntu2
wget --no-verbose \
http://launchpadlibrarian.net/230069137/libmpfr4_3.1.3-2_amd64.deb \
http://launchpadlibrarian.net/253728424/libasan1_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/253728426/libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728432/libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728314/gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/253728399/cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728404/gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
http://launchpadlibrarian.net/253728401/g++-4.9_4.9.3-13ubuntu2_amd64.deb
sudo dpkg -i \
libmpfr4_3.1.3-2_amd64.deb \
libasan1_4.9.3-13ubuntu2_amd64.deb \
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \
gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
cpp-4.9_4.9.3-13ubuntu2_amd64.deb \
gcc-4.9_4.9.3-13ubuntu2_amd64.deb \
g++-4.9_4.9.3-13ubuntu2_amd64.deb
if: ${{ matrix.cxx == 'g++-4.9' }}
- name: Install Clang 3.6
run: |
sudo apt update
sudo apt install libtinfo5
# https://code.launchpad.net/ubuntu/xenial/amd64/clang-3.6/1:3.6.2-3ubuntu2
wget --no-verbose \
http://launchpadlibrarian.net/230019046/libffi6_3.2.1-4_amd64.deb \
http://launchpadlibrarian.net/445346109/libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346128/libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346113/libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346131/libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/445346022/libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/254405108/libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
http://launchpadlibrarian.net/254405097/libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
http://launchpadlibrarian.net/254405101/libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
http://launchpadlibrarian.net/254405091/clang-3.6_3.6.2-3ubuntu2_amd64.deb
sudo dpkg -i \
libffi6_3.2.1-4_amd64.deb \
libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \
libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \
libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \
libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \
gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \
clang-3.6_3.6.2-3ubuntu2_amd64.deb
if: ${{ matrix.cxx == 'clang++-3.6' }}
- name: Add repositories for newer GCC
run: |
sudo apt-add-repository ppa:ubuntu-toolchain-r/test
if: ${{ matrix.cxx == 'g++-11' || matrix.cxx == 'g++-13' }}
if: ${{ matrix.cxx == 'g++-13' }}
- name: Add Ubuntu mirrors
run: |
# Github Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt
sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list
# GitHub Actions caching proxy is at times unreliable
# see https://github.com/actions/runner-images/issues/7048.
mirrors=/etc/apt/mirrors.txt
printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | \
sudo tee $mirrors
curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append $mirrors
sudo sed -i \
"s~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:$mirrors~" \
/etc/apt/sources.list
- name: Create Build Environment
- name: Create build environment
run: |
sudo apt update
${{matrix.install}}
@ -100,10 +156,12 @@ jobs:
CXX: ${{matrix.cxx}}
CXXFLAGS: ${{matrix.cxxflags}}
run: |
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE
cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \
-DCMAKE_CXX_STANDARD=${{matrix.std}} \
-DCMAKE_CXX_VISIBILITY_PRESET=hidden \
-DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \
-DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \
${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE
- name: Build
working-directory: ${{runner.workspace}}/build

View File

@ -9,13 +9,10 @@ jobs:
build:
strategy:
matrix:
os: [macos-13, macos-14]
os: [macos-14]
build_type: [Debug, Release]
std: [11, 17, 20]
std: [11, 17, 20, 23]
shared: [""]
exclude:
- { os: macos-13, std: 11 }
- { os: macos-13, std: 17 }
include:
- os: macos-14
std: 23
@ -25,10 +22,10 @@ jobs:
runs-on: '${{ matrix.os }}'
steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'
run: sudo systemsetup -settimezone 'Europe/Minsk'
- name: Select Xcode 14.3 (macOS 13)
run: sudo xcode-select -s "/Applications/Xcode_14.3.app"

View File

@ -29,12 +29,12 @@ jobs:
steps:
- name: "Checkout code"
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif
@ -52,7 +52,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: SARIF file
path: results.sarif
@ -60,6 +60,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
with:
sarif_file: results.sarif

View File

@ -10,22 +10,18 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
# windows-2019 has MSVC 2019 installed;
# windows-2022 has MSVC 2022 installed:
# https://github.com/actions/virtual-environments.
os: [windows-2019]
os: [windows-2022]
platform: [Win32, x64]
toolset: [v141, v142]
toolset: [v142]
standard: [14, 17, 20]
shared: ["", -DBUILD_SHARED_LIBS=ON]
build_type: [Debug, Release]
exclude:
- { toolset: v141, standard: 20 }
- { toolset: v142, standard: 14 }
- { platform: Win32, toolset: v141 }
- { platform: Win32, standard: 14 }
- { platform: Win32, standard: 20 }
- { platform: x64, toolset: v141, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 14, shared: -DBUILD_SHARED_LIBS=ON }
- { platform: x64, standard: 20, shared: -DBUILD_SHARED_LIBS=ON }
include:
@ -36,10 +32,10 @@ jobs:
standard: 20
steps:
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
run: tzutil /s "FLE Standard Time"
- name: Create Build Environment
run: cmake -E make_directory ${{runner.workspace}}/build
@ -76,14 +72,14 @@ jobs:
sys: [ mingw64, ucrt64 ]
steps:
- name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time"
run: tzutil /s "FLE Standard Time"
shell: cmd
- uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0
- uses: msys2/setup-msys2@40677d36a502eb2cf0fb808cc9dec31bf6152638 # v2.28.0
with:
release: false
msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Configure
run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug
env: { LDFLAGS: -fuse-ld=lld }

3
.gitignore vendored
View File

@ -3,11 +3,14 @@
*.xcodeproj
*~
.vscode/
.cache/
.vs/
/CMakeScripts
/Testing
/_CPack_Packages
/install_manifest.txt
CMakeCache.txt
CMakeUserPresets.json
CMakeFiles
CPack*.cmake
CTestTestfile.cmake

View File

@ -9,7 +9,9 @@ endif ()
# or if it is the master project.
if (NOT DEFINED FMT_MASTER_PROJECT)
set(FMT_MASTER_PROJECT OFF)
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
# NOTE: source vs current_source detection is unreliable
# this heuristic is more generally applicable esp w.r.t FetchContent
if (NOT DEFINED PROJECT_NAME)
set(FMT_MASTER_PROJECT ON)
message(STATUS "CMake version: ${CMAKE_VERSION}")
endif ()
@ -27,13 +29,16 @@ endfunction()
# DEPRECATED! Should be merged into add_module_library.
function(enable_module target)
if (MSVC)
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI}
INTERFACE /reference fmt=${BMI})
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif()
endif ()
endfunction()
set(FMT_USE_CMAKE_MODULES FALSE)
@ -69,8 +74,6 @@ function(add_module_library name)
target_compile_options(${name} PUBLIC -fmodules-ts)
endif ()
target_compile_definitions(${name} PRIVATE FMT_MODULE)
if (FMT_USE_CMAKE_MODULES)
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
FILES ${sources})
@ -160,7 +163,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
# Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ON)
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@ -201,8 +204,7 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif ()
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
include(CheckCXXCompilerFlag)
include(JoinPaths)
@ -295,6 +297,7 @@ function(add_headers VAR)
endfunction()
# Define the fmt library, its includes and the needed defines.
set(FMT_HEADERS)
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
format-inl.h os.h ostream.h printf.h ranges.h std.h
xchar.h)
@ -427,7 +430,7 @@ if (FMT_INSTALL)
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS}
COMPONENT core
COMPONENT fmt_core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
@ -443,13 +446,13 @@ if (FMT_INSTALL)
# Install version, config and target files.
install(FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR}
COMPONENT core)
COMPONENT fmt_core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::
COMPONENT core)
COMPONENT fmt_core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT core)
COMPONENT fmt_core)
endif ()
function(add_doc_target)
@ -486,7 +489,7 @@ function(add_doc_target)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT doc OPTIONAL)
COMPONENT fmt_doc OPTIONAL)
endfunction()
if (FMT_DOC)

View File

@ -1,3 +1,436 @@
# 12.1.0 - 2025-10-29
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
gcc (https://github.com/fmtlib/fmt/issues/4573).
- Made `std::variant` and `std::expected` formatters work with `format_as`
(https://github.com/fmtlib/fmt/issues/4574,
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
- Made `fmt::join<string_view>` work with C++ modules
(https://github.com/fmtlib/fmt/issues/4379,
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
- Fixed a compatibility issue with C++ modules in clang
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
- Added support for cv-qualified types to the `std::optional` formatter
(https://github.com/fmtlib/fmt/issues/4561,
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
- Added demangling support (used in exception and `std::type_info` formatters)
for libc++ and clang-cl
(https://github.com/fmtlib/fmt/issues/4542,
https://github.com/fmtlib/fmt/pull/4560,
https://github.com/fmtlib/fmt/issues/4568,
https://github.com/fmtlib/fmt/pull/4571).
Thanks @FatihBAKIR and @rohitsutreja.
- Switched to global `malloc`/`free` to enable allocator customization
(https://github.com/fmtlib/fmt/issues/4569,
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
- Fixed compilation with locales disabled in the header-only mode
(https://github.com/fmtlib/fmt/issues/4550).
- Fixed compilation with clang 21 and `-std=c++20`
(https://github.com/fmtlib/fmt/issues/4552).
- Fixed a dynamic linking issue with clang-cl
(https://github.com/fmtlib/fmt/issues/4576,
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
- Fixed a warning suppression leakage on gcc
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
- Made more internal color APIs `constexpr`
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
- Fixed compatibility with clang as a host compiler for NVCC
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
- Fixed various warnings and lint issues
(https://github.com/fmtlib/fmt/issues/4565,
https://github.com/fmtlib/fmt/pull/4572,
https://github.com/fmtlib/fmt/pull/4557).
Thanks @LiangHuDream and @teruyamato0731.
- Improved documentation
(https://github.com/fmtlib/fmt/issues/4549,
https://github.com/fmtlib/fmt/pull/4551,
https://github.com/fmtlib/fmt/issues/4566,
https://github.com/fmtlib/fmt/pull/4567,
https://github.com/fmtlib/fmt/pull/4578,).
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
# 12.0.0 - 2025-09-17
- Optimized the default floating point formatting
(https://github.com/fmtlib/fmt/issues/3675,
https://github.com/fmtlib/fmt/issues/4516). In particular, formatting a
`double` with format string compilation into a stack allocated buffer is
more than 60% faster in version 12.0 compared to 11.2 according to
[dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark):
```
Function Time (ns) Speedup
fmt11 34.471 1.00x
fmt12 21.000 1.64x
```
<img width="766" height="609" src="https://github.com/user-attachments/assets/d7d768ad-7543-468c-b0bb-449abf73b31b" />
- Added `constexpr` support to `fmt::format`. For example:
```c++
#include <fmt/compile.h>
using namespace fmt::literals;
std::string s = fmt::format(""_cf, 42);
```
now works at compile time provided that `std::string` supports `constexpr`
(https://github.com/fmtlib/fmt/issues/3403,
https://github.com/fmtlib/fmt/pull/4456). Thanks @msvetkin.
- Added `FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time.
For example:
```c++
#include <fmt/compile.h>
constexpr auto s = FMT_STATIC_FORMAT("{}", 42);
```
compiles to just
```s
__ZL1s:
.asciiz "42"
```
It can be accessed as a C string with `s.c_str()` or as a string view with
`s.str()`.
- Improved C++20 module support
(https://github.com/fmtlib/fmt/pull/4451,
https://github.com/fmtlib/fmt/pull/4459,
https://github.com/fmtlib/fmt/pull/4476,
https://github.com/fmtlib/fmt/pull/4488,
https://github.com/fmtlib/fmt/issues/4491,
https://github.com/fmtlib/fmt/pull/4495).
Thanks @arBmind, @tkhyn, @Mishura4, @anonymouspc and @autoantwort.
- Switched to using estimated display width in precision. For example:
```c++
fmt::print("|{:.4}|\n|1234|\n", "🐱🐱🐱");
```
prints
![](https://github.com/user-attachments/assets/6c4446b3-13eb-43b9-b74a-b4543540ad6a)
because `🐱` has an estimated width of 2
(https://github.com/fmtlib/fmt/issues/4272,
https://github.com/fmtlib/fmt/pull/4443,
https://github.com/fmtlib/fmt/pull/4475).
Thanks @nikhilreddydev and @localspook.
- Fix interaction between debug presentation, precision, and width for strings
(https://github.com/fmtlib/fmt/pull/4478). Thanks @localspook.
- Implemented allocator propagation on `basic_memory_buffer` move
(https://github.com/fmtlib/fmt/issues/4487,
https://github.com/fmtlib/fmt/pull/4490). Thanks @toprakmurat.
- Fixed an ambiguity between `std::reference_wrapper<T>` and `format_as`
formatters (https://github.com/fmtlib/fmt/issues/4424,
https://github.com/fmtlib/fmt/pull/4434). Thanks @jeremy-rifkin.
- Removed the following deprecated APIs:
- `has_formatter`: use `is_formattable` instead,
- `basic_format_args::parse_context_type`,
`basic_format_args::formatter_type` and similar aliases in context types,
- wide stream overload of `fmt::printf`,
- wide stream overloads of `fmt::print` that take text styles,
- `is_*char` traits,
- `fmt::localtime`.
- Deprecated wide overloads of `fmt::fprintf` and `fmt::sprintf`.
- Improved diagnostics for the incorrect usage of `fmt::ptr`
(https://github.com/fmtlib/fmt/pull/4453). Thanks @TobiSchluter.
- Made handling of ANSI escape sequences more efficient
(https://github.com/fmtlib/fmt/pull/4511,
https://github.com/fmtlib/fmt/pull/4528).
Thanks @localspook and @Anas-Hamdane.
- Fixed a buffer overflow on all emphasis flags set
(https://github.com/fmtlib/fmt/pull/4498). Thanks @dominicpoeschko.
- Fixed an integer overflow for precision close to the max `int` value.
- Fixed compatibility with WASI (https://github.com/fmtlib/fmt/issues/4496,
https://github.com/fmtlib/fmt/pull/4497). Thanks @whitequark.
- Fixed `back_insert_iterator` detection, preventing a fallback on slower path
that handles arbitrary iterators (https://github.com/fmtlib/fmt/issues/4454).
- Fixed handling of invalid glibc `FILE` buffers
(https://github.com/fmtlib/fmt/issues/4469).
- Added `wchar_t` support to the `std::byte` formatter
(https://github.com/fmtlib/fmt/issues/4479,
https://github.com/fmtlib/fmt/pull/4480). Thanks @phprus.
- Changed component prefix from `fmt-` to `fmt_` for compatibility with
NSIS/CPack on Windows, e.g. `fmt-doc` changed to `fmt_doc`
(https://github.com/fmtlib/fmt/issues/4441,
https://github.com/fmtlib/fmt/pull/4442). Thanks @n-stein.
- Added the `FMT_CUSTOM_ASSERT_FAIL` macro to simplify providing a custom
`fmt::assert_fail` implementation (https://github.com/fmtlib/fmt/pull/4505).
Thanks @HazardyKnusperkeks.
- Switched to `FMT_THROW` on reporting format errors so that it can be
overriden by users when exceptions are disabled
(https://github.com/fmtlib/fmt/pull/4521). Thanks @HazardyKnusperkeks.
- Improved master project detection and disabled install targets when using
{fmt} as a subproject by default (https://github.com/fmtlib/fmt/pull/4536).
Thanks @crueter.
- Made various code improvements
(https://github.com/fmtlib/fmt/pull/4445,
https://github.com/fmtlib/fmt/pull/4448,
https://github.com/fmtlib/fmt/pull/4473,
https://github.com/fmtlib/fmt/pull/4522).
Thanks @localspook, @tchaikov and @way4sahil.
- Added Conan instructions to the docs
(https://github.com/fmtlib/fmt/pull/4537). Thanks @uilianries.
- Removed Bazel files to avoid issues with downstream packaging
(https://github.com/fmtlib/fmt/pull/4530). Thanks @mering.
- Added more entries for generated files to `.gitignore`
(https://github.com/fmtlib/fmt/pull/4355,
https://github.com/fmtlib/fmt/pull/4512).
Thanks @dinomight and @localspook.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/pull/4447,
https://github.com/fmtlib/fmt/issues/4470,
https://github.com/fmtlib/fmt/pull/4474,
https://github.com/fmtlib/fmt/pull/4477,
https://github.com/fmtlib/fmt/pull/4471,
https://github.com/fmtlib/fmt/pull/4483,
https://github.com/fmtlib/fmt/pull/4515,
https://github.com/fmtlib/fmt/issues/4533,
https://github.com/fmtlib/fmt/pull/4534).
Thanks @dodomorandi, @localspook, @remyjette, @Tomek-Stolarczyk, @Mishura4,
@mattiasljungstrom and @FatihBAKIR.
# 11.2.0 - 2025-05-03
- Added the `s` specifier for `std::error_code`. It allows formatting an error
message as a string. For example:
```c++
#include <fmt/std.h>
int main() {
auto ec = std::make_error_code(std::errc::no_such_file_or_directory);
fmt::print("{:s}\n", ec);
}
```
prints
```
No such file or directory
```
(The actual message is platform-specific.)
- Fixed formatting of `std::chrono::local_time` and `tm`
(https://github.com/fmtlib/fmt/issues/3815,
https://github.com/fmtlib/fmt/issues/4350).
For example ([godbolt](https://www.godbolt.org/z/8o4b1PPn5)):
```c++
#include <fmt/chrono.h>
int main() {
std::chrono::zoned_time zt(
std::chrono::current_zone(),
std::chrono::system_clock::now());
fmt::print("{}", zt.get_local_time());
}
```
is now formatted consistenly across platforms.
- Added diagnostics for cases when timezone information is not available.
For example:
```c++
fmt::print("{:Z}", std::chrono::local_seconds());
```
now gives a compile-time error.
- Deprecated `fmt::localtime` in favor of `std::localtime`.
- Fixed compilation with GCC 15 and C++20 modules enabled
(https://github.com/fmtlib/fmt/pull/4347). Thanks @tkhyn.
- Fixed handling of named arguments in format specs
(https://github.com/fmtlib/fmt/issues/4360,
https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight.
- Added error reporting for duplicate named arguments
(https://github.com/fmtlib/fmt/issues/4282,
https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight.
- Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0`
(https://github.com/fmtlib/fmt/issues/4375,
https://github.com/fmtlib/fmt/issues/4394).
- Optimized `text_style` using bit packing
(https://github.com/fmtlib/fmt/pull/4363). Thanks @localspook.
- Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180,
https://github.com/fmtlib/fmt/pull/4383). Thanks @localspook.
- Fixed a flush issue in `fmt::print` when using libstdc++
(https://github.com/fmtlib/fmt/issues/4398).
- Fixed `fmt::println` usage with `FMT_ENFORCE_COMPILE_STRING` and legacy
compile-time checks (https://github.com/fmtlib/fmt/pull/4407).
Thanks @madmaxoft.
- Removed legacy header `fmt/core.h` from docs
(https://github.com/fmtlib/fmt/pull/4421,
https://github.com/fmtlib/fmt/pull/4422). Thanks @krzysztofkortas.
- Worked around limitations of `__builtin_strlen` during constant evaluation
(https://github.com/fmtlib/fmt/issues/4423,
https://github.com/fmtlib/fmt/pull/4429). Thanks @BRevzin.
- Worked around a bug in MSVC v141 (https://github.com/fmtlib/fmt/issues/4412,
https://github.com/fmtlib/fmt/pull/4413). Thanks @hirohira9119.
- Removed the `fmt_detail` namespace
(https://github.com/fmtlib/fmt/issues/4324).
- Removed specializations of `std::is_floating_point` in tests
(https://github.com/fmtlib/fmt/issues/4417).
- Fixed a CMake error when setting `CMAKE_MODULE_PATH` in the pedantic mode
(https://github.com/fmtlib/fmt/pull/4426). Thanks @rlalik.
- Updated the Bazel config (https://github.com/fmtlib/fmt/pull/4400).
Thanks @Vertexwahn.
# 11.1.4 - 2025-02-26
- Fixed ABI compatibility with earlier 11.x versions on Windows
(https://github.com/fmtlib/fmt/issues/4359).
- Improved the logic of switching between fixed and exponential format for
`float` (https://github.com/fmtlib/fmt/issues/3649).
- Moved `is_compiled_string` to the public API
(https://github.com/fmtlib/fmt/issues/4335,
https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
- Simplified implementation of `operator""_cf`
(https://github.com/fmtlib/fmt/pull/4349). Thanks @localspook.
- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329).
Thanks @localspook.
- Fixed handling of BMI paths with the Ninja generator
(https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn.
- Fixed gcc 8.3 compile errors (https://github.com/fmtlib/fmt/issues/4331,
https://github.com/fmtlib/fmt/pull/4336). Thanks @sergiud.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/pull/4356).
Thanks @dinomight.
# 11.1.3 - 2025-01-25
- Fixed compilation on GCC 9.4 (https://github.com/fmtlib/fmt/issues/4313).
- Worked around an internal compiler error when using C++20 modules with GCC
14.2 and earlier (https://github.com/fmtlib/fmt/issues/4295).
- Worked around a bug in GCC 6 (https://github.com/fmtlib/fmt/issues/4318).
- Fixed an issue caused by instantiating `formatter<const T>`
(https://github.com/fmtlib/fmt/issues/4303,
https://github.com/fmtlib/fmt/pull/4325). Thanks @timsong-cpp.
- Fixed formatting into `std::ostreambuf_iterator` when using format string
compilation (https://github.com/fmtlib/fmt/issues/4309,
https://github.com/fmtlib/fmt/pull/4312). Thanks @phprus.
- Restored a constraint on the map formatter so that it correctly reports as
unformattable when the element is (https://github.com/fmtlib/fmt/pull/4326).
Thanks @timsong-cpp.
- Reduced the size of format specs (https://github.com/fmtlib/fmt/issues/4298).
- Readded `args()` to `fmt::format_context`
(https://github.com/fmtlib/fmt/issues/4307,
https://github.com/fmtlib/fmt/pull/4310). Thanks @Erroneous1.
- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/issues/4314,
https://github.com/fmtlib/fmt/pull/4322). Thanks @ZehMatt.
- Fixed a pedantic mode error in the CMake config
(https://github.com/fmtlib/fmt/pull/4327). Thanks @rlalik.
# 11.1.2 - 2025-01-12
- Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4292).
- Added `wchar_t` support to the `std::bitset` formatter
(https://github.com/fmtlib/fmt/issues/4285,
https://github.com/fmtlib/fmt/pull/4286,
https://github.com/fmtlib/fmt/issues/4289,
https://github.com/fmtlib/fmt/pull/4290). Thanks @phprus.
- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via
`add_subdirectory` (https://github.com/fmtlib/fmt/issues/4283).
- Updated docs for meson (https://github.com/fmtlib/fmt/pull/4291).
Thanks @trim21.
- Fixed a compilation error in chrono on nvcc
(https://github.com/fmtlib/fmt/issues/4297,
https://github.com/fmtlib/fmt/pull/4301). Thanks @breyerml.
- Fixed various warnings
(https://github.com/fmtlib/fmt/pull/4288,
https://github.com/fmtlib/fmt/pull/4299). Thanks @GamesTrap and @edo9300.
# 11.1.1 - 2024-12-27
- Fixed ABI compatibility with earlier 11.x versions

View File

@ -4,14 +4,15 @@
[![image](https://github.com/fmtlib/fmt/workflows/macos/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
[![image](https://github.com/fmtlib/fmt/workflows/windows/badge.svg)](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
[![fmt is continuously fuzzed at oss-fuzz](https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/8880/badge)](https://www.bestpractices.dev/projects/8880)
[![image](https://api.securityscorecards.dev/projects/github.com/fmtlib/fmt/badge)](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
[![Ask questions at StackOverflow with the tag fmt](https://img.shields.io/badge/stackoverflow-fmt-blue.svg)](https://stackoverflow.com/questions/tagged/fmt)
**{fmt}** is an open-source formatting library providing a fast and safe
alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to one of the funds
that help victims of the war in Ukraine: <https://www.stopputin.net/>.
that help victims of the war in Ukraine: <https://u24.gov.ua/>.
[Documentation](https://fmt.dev)
@ -47,7 +48,7 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
hundred million integers to strings per
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
- Small code size both in terms of source code with the minimum
configuration consisting of just three files, `core.h`, `format.h`
configuration consisting of just three files, `base.h`, `format.h`
and `format-inl.h`, and compiled code; see [Compile time and code
bloat](#compile-time-and-code-bloat)
- Reliability: the library has an extensive set of
@ -74,7 +75,7 @@ See the [documentation](https://fmt.dev) for more details.
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
``` c++
#include <fmt/core.h>
#include <fmt/base.h>
int main() {
fmt::print("Hello, world!\n");
@ -149,8 +150,8 @@ int main() {
}
```
This can be [5 to 9 times faster than
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
This can be [up to 9 times faster than `fprintf`](
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles**
@ -177,17 +178,17 @@ Output on a modern terminal with Unicode support:
| Library | Method | Run Time, s |
|-------------------|---------------|-------------|
| libc | printf | 0.91 |
| libc++ | std::ostream | 2.49 |
| {fmt} 9.1 | fmt::print | 0.74 |
| Boost Format 1.80 | boost::format | 6.26 |
| Folly Format | folly::format | 1.87 |
| libc | printf | 0.66 |
| libc++ | std::ostream | 1.63 |
| {fmt} 12.1 | fmt::print | 0.44 |
| Boost Format 1.88 | boost::format | 3.89 |
| Folly Format | folly::format | 1.28 |
{fmt} is the fastest of the benchmarked methods, \~20% faster than
{fmt} is the fastest of the benchmarked methods, \~50% faster than
`printf`.
The above results were generated by building `tinyformat_test.cpp` on
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
taking the best of three runs. In the test, the format string
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
times with output sent to `/dev/null`; for further details refer to the
@ -216,11 +217,11 @@ in the following tables.
**Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------|
|-----------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 |
| IOStreams | 25.9 | 98 | 84 |
| fmt 83652df | 4.8 | 54 | 50 |
| tinyformat | 29.1 | 161 | 136 |
| IOStreams | 28.4 | 98 | 84 |
| {fmt} `1122268` | 5.0 | 54 | 50 |
| tinyformat | 32.6 | 164 | 136 |
| Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
@ -229,12 +230,12 @@ binary size (within a rounding error on this system).
**Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|---------------|-----------------|----------------------|--------------------|
|-----------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 |
| IOStreams | 23.4 | 92 | 68 |
| {fmt} 83652df | 4.4 | 89 | 85 |
| tinyformat | 24.5 | 204 | 161 |
| Boost Format | 36.4 | 831 | 462 |
| IOStreams | 27.0 | 88 | 68 |
| {fmt} `1122268` | 4.7 | 87 | 84 |
| tinyformat | 28.1 | 185 | 145 |
| Boost Format | 38.9 | 678 | 381 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a

View File

@ -674,7 +674,7 @@
https://github.com/fmtlib/fmt/issues/1747,
https://github.com/fmtlib/fmt/pull/1750).
Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10,
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink,
@daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime,
@tohammer and @0x8000-0000.
# 6.2.1 - 2020-05-09

View File

@ -79,6 +79,8 @@ time formatting and [`fmt/std.h`](#std-api) for other standard library types.
There are two ways to make a user-defined type formattable: providing a
`format_as` function or specializing the `formatter` struct template.
Formatting of non-void pointer types is intentionally disallowed and they
cannot be made formattable via either extension API.
Use `format_as` if you want to make your type formattable as some other
type with the same format specifiers. The `format_as` function should
@ -220,7 +222,7 @@ You can also write a formatter for a hierarchy of classes:
```c++
// demo.h:
#include <type_traits>
#include <fmt/core.h>
#include <fmt/format.h>
struct A {
virtual ~A() {}
@ -403,7 +405,7 @@ All formatting is locale-independent by default. Use the `'L'` format
specifier to insert the appropriate number separator characters from the
locale:
#include <fmt/core.h>
#include <fmt/format.h>
#include <locale>
std::locale::global(std::locale("en_US.UTF-8"));
@ -413,11 +415,11 @@ locale:
that take `std::locale` as a parameter. The locale type is a template
parameter to avoid the expensive `<locale>` include.
::: format(detail::locale_ref, format_string<T...>, T&&...)
::: format(locale_ref, format_string<T...>, T&&...)
::: format_to(OutputIt, detail::locale_ref, format_string<T...>, T&&...)
::: format_to(OutputIt, locale_ref, format_string<T...>, T&&...)
::: formatted_size(detail::locale_ref, format_string<T...>, T&&...)
::: formatted_size(locale_ref, format_string<T...>, T&&...)
<a id="legacy-checks"></a>
### Legacy Compile-Time Checks
@ -473,9 +475,9 @@ chrono-format-specifications).
#include <fmt/chrono.h>
int main() {
std::time_t t = std::time(nullptr);
auto now = std::chrono::system_clock::now();
fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t));
fmt::print("The date is {:%Y-%m-%d}.\n", now);
// Output: The date is 2020-11-07.
// (with 2020-11-07 replaced by the current date)
@ -488,8 +490,6 @@ chrono-format-specifications).
// Output: strftime-like format: 03:15:30
}
::: localtime(std::time_t)
::: gmtime(std::time_t)
<a id="std-api"></a>
@ -549,38 +549,69 @@ fmt::print("{}", +s.bit);
This is a known limitation of "perfect" forwarding in C++.
<a id="compile-api"></a>
## Format String Compilation
## Compile-Time Support
`fmt/compile.h` provides format string compilation and compile-time
(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf`
user-defined literal defined in namespace `fmt::literals`. Format strings
marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into
efficient formatting code at compile-time. This supports arguments of built-in
and string types as well as user-defined types with `format` functions taking
and string types as well as user-defined types with `format` methods taking
the format context type as a template parameter in their `formatter`
specializations. For example:
specializations. For example ([run](https://www.godbolt.org/z/3c13erEoq)):
struct point {
double x;
double y;
};
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx);
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const;
auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
};
using namespace fmt::literals;
std::string s = fmt::format("{}"_cf, point(4, 2));
Format string compilation can generate more binary code compared to the
default API and is only recommended in places where formatting is a
performance bottleneck.
::: FMT_COMPILE
The same APIs support formatting at compile time e.g. in `constexpr`
and `consteval` functions. Additionally there is an experimental
`FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time. Compile-time formatting works with built-in
and user-defined formatters that have `constexpr` `format` methods.
Example:
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
constexpr auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
};
constexpr auto s = FMT_STATIC_FORMAT("{}", point(4, 2));
const char* cstr = s.c_str(); // Points the static string "(4, 2)".
::: operator""_cf
::: FMT_COMPILE
::: FMT_STATIC_FORMAT
<a id="color-api"></a>
## Terminal Colors and Text Styles
`fmt/color.h` provides support for terminal color and text style output.
::: print(const text_style&, format_string<T...>, T&&...)
::: print(text_style, format_string<T...>, T&&...)
::: fg(detail::color_type)
@ -593,6 +624,8 @@ performance bottleneck.
::: ostream
::: output_file(cstring_view, T...)
::: windows_error
<a id="ostream-api"></a>
@ -643,9 +676,9 @@ if an argument type doesn't match its format specification.
::: printf(string_view, const T&...)
::: fprintf(std::FILE*, const S&, const T&...)
::: fprintf(std::FILE*, string_view, const T&...)
::: sprintf(const S&, const T&...)
::: sprintf(string_view, const T&...)
<a id="xchar-api"></a>
## Wide Strings
@ -653,8 +686,6 @@ if an argument type doesn't match its format specification.
The optional header `fmt/xchar.h` provides support for `wchar_t` and
exotic character types.
::: is_char
::: wstring_view
::: wformat_context
@ -669,5 +700,63 @@ following differences:
- Names are defined in the `fmt` namespace instead of `std` to avoid
collisions with standard library implementations.
- Width calculation doesn't use grapheme clusterization. The latter has
been implemented in a separate branch but hasn't been integrated yet.
- The default floating-point representation in {fmt} uses the smallest
precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters
(ignoring redundant digits and sign in exponent) and may produce more
decimal digits than necessary.
## Configuration Options
{fmt} provides configuration via CMake options and preprocessor macros to
enable or disable features and to optimize for binary size. For example, you
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
configuring CMake.
### CMake Options
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
Windows/MSVC. Unicode support is always enabled on other platforms.
### Macros
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
alternative to using the `fmt::fmt-header-only` CMake target.
Default: not defined.
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
Default: `1` (`0` if compiled with `-fno-exceptions`).
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
custom `fmt::assert_fail` function which is called on assertion failures and,
if exceptions are disabled, on runtime errors. Default: `0`.
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
arithmetic and string types other than `int`. This reduces library size at
the cost of per-call overhead. Default: `1`.
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
- `0` - off (default)
- `1` - disables locale support and applies some optimizations
- `2` - disables some Unicode features, named arguments and applies more
aggressive optimizations
### Binary Size Optimization
To minimize the binary footprint of {fmt} as much as possible at the cost of
some features, you can use the following configuration:
- CMake options:
- `FMT_OS=OFF`
- Macros:
- `FMT_BUILTIN_TYPES=0`
- `FMT_OPTIMIZE_SIZE=2`

View File

@ -20,6 +20,7 @@
margin-left: 1em;
}
code,
pre > code.decl {
white-space: pre-wrap;
}

View File

@ -78,6 +78,17 @@ community contributors. If the version is out of date, please [create an
issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg
repository. -->
### Conan
You can download and install {fmt} using the [Conan](https://conan.io/) package manager:
conan install -r conancenter --requires="fmt/[*]" --build=missing
<!-- The {fmt} package in Conan Center is maintained by
[ConanCenterIndex](https://github.com/conan-io/conan-center-index) community.
If the version is out of date or the package does not work,
please create an issue or pull request on the Conan Center Index repository. -->
## Building from Source
CMake works by generating native makefiles or project files that can be
@ -202,7 +213,7 @@ For a static build, use the following subproject definition:
For the header-only version, use:
fmt = subproject('fmt')
fmt = subproject('fmt', default_options: ['header-only=true'])
fmt_dep = fmt.get_variable('fmt_header_only_dep')
### Android NDK

View File

@ -76,7 +76,7 @@ hide:
<p>
The default is <b>locale-independent</b>, but you can opt into localized
formatting and {fmt} makes it work with Unicode, addressing issues in the
standard libary.
standard library.
</p>
</div>
@ -122,7 +122,7 @@ hide:
</p>
<p>
The library is highly portable and requires only a minimal <b>subset of
C++11</b> features which are available in GCC 4.9, Clang 3.4, MSVC 19.10
C++11</b> features which are available in GCC 4.9, Clang 3.6, MSVC 19.10
(2017) and later. Newer compiler and standard library features are used
if available, and enable additional functionality.
</p>

View File

@ -718,7 +718,7 @@ These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
Format specifications for range types have the following syntax:
<pre><code class="language-json"
>range_format_spec ::= ["n"][range_type][range_underlying_spec]</code>
>range_format_spec ::= ["n"][range_type][":" range_underlying_spec]</code>
</pre>
The `'n'` option formats the range without the opening and closing brackets.
@ -761,14 +761,16 @@ fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [h, e, l, l, o]
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [104, 101, 108, 108, 111]
fmt::print("{:n:f}", std::array{std::numbers::pi, std::numbers::e});
// Output: 3.141593, 2.718282
```
## Format Examples
This section contains examples of the format syntax and comparison with
the printf formatting.
the `printf` formatting.
In most of the cases the syntax is similar to the printf formatting,
In most of the cases the syntax is similar to the `printf` formatting,
with the addition of the `{}` and with `:` used instead of `%`. For
example, `"%03.2f"` can be translated to `"{:03.2f}"`.

View File

@ -71,7 +71,7 @@ class dynamic_arg_list {
* It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`.
*/
template <typename Context> class dynamic_format_arg_store {
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
private:
using char_type = typename Context::char_type;
@ -212,7 +212,7 @@ template <typename Context> class dynamic_format_arg_store {
}
/// Returns the number of elements in the store.
size_t size() const noexcept { return data_.size(); }
auto size() const noexcept -> size_t { return data_.size(); }
};
FMT_END_NAMESPACE

View File

@ -21,7 +21,7 @@
#endif
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 110101
#define FMT_VERSION 120100
// Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__)
@ -96,9 +96,9 @@
// Detect C++14 relaxed constexpr.
#ifdef FMT_USE_CONSTEXPR
// Use the provided definition.
#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L
// GCC only allows throw in constexpr since version 6:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371.
#elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L
// GCC only allows constexpr member functions in non-literal types since 7.2:
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297.
# define FMT_USE_CONSTEXPR 1
#elif FMT_ICC_VERSION
# define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628
@ -114,7 +114,9 @@
#endif
// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated.
#if !defined(__cpp_lib_is_constant_evaluated)
#ifdef FMT_USE_CONSTEVAL
// Use the provided definition.
#elif !defined(__cpp_lib_is_constant_evaluated)
# define FMT_USE_CONSTEVAL 0
#elif FMT_CPLUSPLUS < 201709L
# define FMT_USE_CONSTEVAL 0
@ -201,28 +203,6 @@
# define FMT_NODISCARD
#endif
#ifdef FMT_DEPRECATED
// Use the provided definition.
#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated)
# define FMT_DEPRECATED [[deprecated]]
#else
# define FMT_DEPRECATED /* deprecated */
#endif
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
#else
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#ifdef NDEBUG
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
#endif
#if FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_VISIBILITY(value) __attribute__((visibility(value)))
#else
@ -249,10 +229,32 @@
# define FMT_MSC_WARNING(...)
#endif
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og"))
#endif
FMT_PRAGMA_CLANG(diagnostic push)
FMT_PRAGMA_GCC(diagnostic push)
#ifdef FMT_ALWAYS_INLINE
// Use the provided definition.
#elif FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_ALWAYS_INLINE inline __attribute__((always_inline))
#else
# define FMT_ALWAYS_INLINE inline
#endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#ifdef NDEBUG
# define FMT_INLINE FMT_ALWAYS_INLINE
#else
# define FMT_INLINE inline
#endif
#ifndef FMT_BEGIN_NAMESPACE
# define FMT_BEGIN_NAMESPACE \
namespace fmt { \
inline namespace v11 {
inline namespace v12 {
# define FMT_END_NAMESPACE \
} \
}
@ -294,15 +296,8 @@
#endif
#define FMT_APPLY_VARIADIC(expr) \
using ignore = int[]; \
(void)ignore { 0, (expr, 0)... }
// Enable minimal optimizations for more compact code in debug mode.
FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__)
FMT_PRAGMA_GCC(optimize("Og"))
#endif
FMT_PRAGMA_CLANG(diagnostic push)
using unused = int[]; \
(void)unused { 0, (expr, 0)... }
FMT_BEGIN_NAMESPACE
@ -325,8 +320,8 @@ using underlying_t = typename std::underlying_type<T>::type;
template <typename T> using decay_t = typename std::decay<T>::type;
using nullptr_t = decltype(nullptr);
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500
// A workaround for gcc 4.9 to make void_t work in a SFINAE context.
#if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION
// A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context.
template <typename...> struct void_t_impl {
using type = void;
};
@ -355,6 +350,9 @@ template <typename T> constexpr auto max_of(T a, T b) -> T {
return a > b ? a : b;
}
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message);
namespace detail {
// Suppresses "unused variable" warnings with the method described in
// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
@ -395,7 +393,7 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
# define FMT_ASSERT(condition, message) \
((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
? (void)0 \
: fmt::detail::assert_fail(__FILE__, __LINE__, (message)))
: ::fmt::assert_fail(__FILE__, __LINE__, (message)))
#endif
#ifdef FMT_USE_INT128
@ -418,8 +416,12 @@ inline auto map(int128_opt) -> monostate { return {}; }
inline auto map(uint128_opt) -> monostate { return {}; }
#endif
#ifndef FMT_USE_BITINT
# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500)
#ifdef FMT_USE_BITINT
// Use the provided definition.
#elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__)
# define FMT_USE_BITINT 1
#else
# define FMT_USE_BITINT 0
#endif
#if FMT_USE_BITINT
@ -462,12 +464,13 @@ enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled };
static_assert(!FMT_UNICODE || use_utf8,
"Unicode support requires compiling with /utf-8");
template <typename T> constexpr const char* narrow(const T*) { return nullptr; }
constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; }
template <typename T> constexpr auto narrow(T*) -> char* { return nullptr; }
constexpr FMT_ALWAYS_INLINE auto narrow(const char* s) -> const char* {
return s;
}
template <typename Char>
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n)
-> int {
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int {
if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);
for (; n != 0; ++s1, ++s2, --n) {
if (*s1 < *s2) return -1;
@ -526,20 +529,20 @@ template <typename Char> class basic_string_view {
constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {}
/// Constructs a string reference object from a C string and a size.
/// Constructs a string view object from a C string and a size.
constexpr basic_string_view(const Char* s, size_t count) noexcept
: data_(s), size_(count) {}
constexpr basic_string_view(nullptr_t) = delete;
/// Constructs a string reference object from a C string.
/// Constructs a string view object from a C string.
#if FMT_GCC_VERSION
FMT_ALWAYS_INLINE
#endif
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value) {
size_ = __builtin_strlen(detail::narrow(s));
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) {
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr.
return;
}
#endif
@ -548,7 +551,7 @@ template <typename Char> class basic_string_view {
size_ = len;
}
/// Constructs a string reference from a `std::basic_string` or a
/// Constructs a string view from a `std::basic_string` or a
/// `std::basic_string_view` object.
template <typename S,
FMT_ENABLE_IF(detail::is_std_string_like<S>::value&& std::is_same<
@ -585,7 +588,6 @@ template <typename Char> class basic_string_view {
return starts_with(basic_string_view<Char>(s));
}
// Lexicographically compare this string reference to other.
FMT_CONSTEXPR auto compare(basic_string_view other) const -> int {
int result =
detail::compare(data_, other.data_, min_of(size_, other.size_));
@ -616,19 +618,6 @@ template <typename Char> class basic_string_view {
using string_view = basic_string_view<char>;
/// Specifies if `T` is an extended character type. Can be specialized by users.
template <typename T> struct is_xchar : std::false_type {};
template <> struct is_xchar<wchar_t> : std::true_type {};
template <> struct is_xchar<char16_t> : std::true_type {};
template <> struct is_xchar<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_xchar<char8_t> : std::true_type {};
#endif
// DEPRECATED! Will be replaced with an alias to prevent specializations.
template <typename T> struct is_char : is_xchar<T> {};
template <> struct is_char<char> : std::true_type {};
template <typename T> class basic_appender;
using appender = basic_appender<char>;
@ -739,13 +728,15 @@ class basic_specs {
max_fill_size = 4
};
size_t data_ = 1 << fill_size_shift;
unsigned data_ = 1 << fill_size_shift;
static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, "");
// Character (code unit) type is erased to prevent template bloat.
char fill_data_[max_fill_size] = {' '};
FMT_CONSTEXPR void set_fill_size(size_t size) {
data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift);
data_ = (data_ & ~fill_size_mask) |
(static_cast<unsigned>(size) << fill_size_shift);
}
public:
@ -779,7 +770,7 @@ class basic_specs {
(static_cast<unsigned>(p) << precision_shift);
}
constexpr bool dynamic() const {
constexpr auto dynamic() const -> bool {
return (data_ & (width_mask | precision_mask)) != 0;
}
@ -842,6 +833,12 @@ class basic_specs {
for (size_t i = 0; i < size; ++i)
fill_data_[i & 3] = static_cast<char>(s[i]);
}
FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) {
set_fill_size(specs.fill_size());
for (size_t i = 0; i < max_fill_size; ++i)
fill_data_[i] = specs.fill_data_[i];
}
};
// Format specifiers for built-in and string types.
@ -913,14 +910,50 @@ template <typename Char = char> class parse_context {
FMT_CONSTEXPR void check_dynamic_spec(int arg_id);
};
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to std::locale to avoid the heavy <locale> include.
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc) : locale_(&loc) {
// Check if std::isalpha is found via ADL to reduce the chance of misuse.
detail::ignore_unused(sizeof(isalpha('x', loc)));
}
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
FMT_END_EXPORT
namespace detail {
// Specifies if `T` is a code unit type.
template <typename T> struct is_code_unit : std::false_type {};
template <> struct is_code_unit<char> : std::true_type {};
template <> struct is_code_unit<wchar_t> : std::true_type {};
template <> struct is_code_unit<char16_t> : std::true_type {};
template <> struct is_code_unit<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_code_unit<char8_t> : bool_constant<is_utf8_enabled> {};
#endif
// Constructs fmt::basic_string_view<Char> from types implicitly convertible
// to it, deducing Char. Explicitly convertible types such as the ones returned
// from FMT_STRING are intentionally excluded.
template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)>
template <typename Char, FMT_ENABLE_IF(is_code_unit<Char>::value)>
constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> {
return s;
}
@ -1024,6 +1057,11 @@ enum {
struct view {};
template <typename T, typename Enable = std::true_type>
struct is_view : std::false_type {};
template <typename T>
struct is_view<T, bool_constant<sizeof(T) != 0>> : std::is_base_of<view, T> {};
template <typename Char, typename T> struct named_arg;
template <typename T> struct is_named_arg : std::false_type {};
template <typename T> struct is_static_named_arg : std::false_type {};
@ -1044,11 +1082,11 @@ template <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {
return (B1 ? 1 : 0) + count<B2, Tail...>();
}
template <typename... Args> constexpr auto count_named_args() -> int {
return count<is_named_arg<Args>::value...>();
template <typename... T> constexpr auto count_named_args() -> int {
return count<is_named_arg<T>::value...>();
}
template <typename... Args> constexpr auto count_static_named_args() -> int {
return count<is_static_named_arg<Args>::value...>();
template <typename... T> constexpr auto count_static_named_args() -> int {
return count<is_static_named_arg<T>::value...>();
}
template <typename Char> struct named_arg_info {
@ -1056,6 +1094,16 @@ template <typename Char> struct named_arg_info {
int id;
};
// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13.
template <typename Char>
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
int named_arg_index,
basic_string_view<Char> arg_name) {
for (int i = 0; i < named_arg_index; ++i) {
if (named_args[i].name == arg_name) report_error("duplicate named arg");
}
}
template <typename Char, typename T, FMT_ENABLE_IF(!is_named_arg<T>::value)>
void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
++arg_index;
@ -1063,6 +1111,7 @@ void init_named_arg(named_arg_info<Char>*, int& arg_index, int&, const T&) {
template <typename Char, typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
void init_named_arg(named_arg_info<Char>* named_args, int& arg_index,
int& named_arg_index, const T& arg) {
check_for_duplicate<Char>(named_args, named_arg_index, arg.name);
named_args[named_arg_index++] = {arg.name, arg_index++};
}
@ -1076,12 +1125,13 @@ template <typename T, typename Char,
FMT_ENABLE_IF(is_static_named_arg<T>::value)>
FMT_CONSTEXPR void init_static_named_arg(named_arg_info<Char>* named_args,
int& arg_index, int& named_arg_index) {
check_for_duplicate<Char>(named_args, named_arg_index, T::name);
named_args[named_arg_index++] = {T::name, arg_index++};
}
// To minimize the number of types we need to deal with, long is translated
// either to int or to long long depending on its size.
enum { long_short = sizeof(long) == sizeof(int) };
enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES };
using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
@ -1113,7 +1163,7 @@ using use_formatter =
bool_constant<(std::is_class<T>::value || std::is_enum<T>::value ||
std::is_union<T>::value || std::is_array<T>::value) &&
!has_to_string_view<T>::value && !is_named_arg<T>::value &&
!use_format_as<T>::value && !use_format_as_member<T>::value>;
!use_format_as<T>::value && !use_format_as_member<U>::value>;
template <typename Char, typename T, typename U = remove_const_t<T>>
auto has_formatter_impl(T* p, buffered_context<Char>* ctx = nullptr)
@ -1148,7 +1198,7 @@ template <typename Char> struct type_mapper {
static auto map(ubitint<N>)
-> conditional_t<N <= 64, unsigned long long, void>;
template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)>
static auto map(T) -> conditional_t<
std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>;
@ -1654,12 +1704,12 @@ template <typename... T> struct arg_pack {};
template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>
class format_string_checker {
private:
type types_[max_of(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)];
type types_[max_of<size_t>(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of<size_t>(1, NUM_NAMED_ARGS)];
compile_parse_context<Char> context_;
using parse_func = auto (*)(parse_context<Char>&) -> const Char*;
parse_func parse_funcs_[max_of(1, NUM_ARGS)];
parse_func parse_funcs_[max_of<size_t>(1, NUM_ARGS)];
public:
template <typename... T>
@ -1698,7 +1748,17 @@ class format_string_checker {
-> const Char* {
context_.advance_to(begin);
if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_);
while (begin != end && *begin != '}') ++begin;
// If id is out of range, it means we do not know the type and cannot parse
// the format at compile time. Instead, skip over content until we finish
// the format spec, accounting for any nested replacements.
for (int bracket_count = 0;
begin != end && (bracket_count > 0 || *begin != '}'); ++begin) {
if (*begin == '{')
++bracket_count;
else if (*begin == '}')
--bracket_count;
}
return begin;
}
@ -1790,15 +1850,19 @@ template <typename T> class buffer {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940
FMT_CONSTEXPR20
#endif
void
append(const U* begin, const U* end) {
void append(const U* begin, const U* end) {
while (begin != end) {
auto size = size_;
auto free_cap = capacity_ - size;
auto count = to_unsigned(end - begin);
try_reserve(size_ + count);
auto free_cap = capacity_ - size_;
if (free_cap < count) count = free_cap;
if (free_cap < count) {
grow_(*this, size + count);
size = size_;
free_cap = capacity_ - size;
count = count < free_cap ? count : free_cap;
}
// A loop is faster than memcpy on small sizes.
T* out = ptr_ + size_;
T* out = ptr_ + size;
for (size_t i = 0; i < count; ++i) out[i] = begin[i];
size_ += count;
begin += count;
@ -1998,6 +2062,17 @@ struct has_back_insert_iterator_container_append<
.append(std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
template <typename OutputIt, typename InputIt, typename = void>
struct has_back_insert_iterator_container_insert_at_end : std::false_type {};
template <typename OutputIt, typename InputIt>
struct has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt,
void_t<decltype(get_container(std::declval<OutputIt>())
.insert(get_container(std::declval<OutputIt>()).end(),
std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
// An optimized version of std::copy with the output value type (T).
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&
@ -2012,6 +2087,8 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
!has_back_insert_iterator_container_append<
OutputIt, InputIt>::value &&
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)>
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
-> OutputIt {
@ -2021,7 +2098,11 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
}
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)>
FMT_ENABLE_IF(!(is_back_insert_iterator<OutputIt>::value &&
(has_back_insert_iterator_container_append<
OutputIt, InputIt>::value ||
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)))>
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
while (begin != end) *out++ = static_cast<T>(*begin++);
return out;
@ -2141,7 +2222,7 @@ template <typename Context> class value {
static_assert(N <= 64, "unsupported _BitInt");
}
template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)>
constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) {
static_assert(
std::is_same<T, char>::value || std::is_same<T, char_type>::value,
@ -2217,7 +2298,7 @@ template <typename Context> class value {
custom.value = const_cast<value_type*>(&x);
#endif
}
custom.format = format_custom<value_type, formatter<value_type, char_type>>;
custom.format = format_custom<value_type>;
}
template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>
@ -2228,10 +2309,10 @@ template <typename Context> class value {
}
// Formats an argument of a custom type, such as a user-defined class.
template <typename T, typename Formatter>
template <typename T>
static void format_custom(void* arg, parse_context<char_type>& parse_ctx,
Context& ctx) {
auto f = Formatter();
auto f = formatter<T, char_type>();
parse_ctx.advance_to(f.parse(parse_ctx));
using qualified_type =
conditional_t<has_formatter<const T, char_type>(), const T, T>;
@ -2255,39 +2336,17 @@ template <> struct is_output_iterator<appender, char> : std::true_type {};
template <typename It, typename T>
struct is_output_iterator<
It, T,
void_t<decltype(*std::declval<decay_t<It>&>()++ = std::declval<T>())>>
: std::true_type {};
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to an std::locale to avoid a heavy <locale> include.
struct locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto get() const -> Locale;
};
enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++),
T>::value>> : std::true_type {};
template <typename> constexpr auto encode_types() -> unsigned long long {
return 0;
}
template <typename Context, typename Arg, typename... Args>
template <typename Context, typename First, typename... T>
constexpr auto encode_types() -> unsigned long long {
return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) |
(encode_types<Context, Args...>() << packed_arg_bits);
return static_cast<unsigned>(stored_type_constant<First, Context>::value) |
(encode_types<Context, T...>() << packed_arg_bits);
}
template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)>
@ -2304,8 +2363,9 @@ template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
unsigned long long DESC>
struct named_arg_store {
// args_[0].named_args points to named_args to avoid bloating format_args.
arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS];
named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS];
arg_t<Context, NUM_ARGS> args[1u + NUM_ARGS];
named_arg_info<typename Context::char_type>
named_args[static_cast<size_t>(NUM_NAMED_ARGS)];
template <typename... T>
FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)
@ -2324,8 +2384,8 @@ struct named_arg_store {
}
named_arg_store(const named_arg_store& rhs) = delete;
named_arg_store& operator=(const named_arg_store& rhs) = delete;
named_arg_store& operator=(named_arg_store&& rhs) = delete;
auto operator=(const named_arg_store& rhs) -> named_arg_store& = delete;
auto operator=(named_arg_store&& rhs) -> named_arg_store& = delete;
operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; }
};
@ -2338,7 +2398,7 @@ struct format_arg_store {
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
using type =
conditional_t<NUM_NAMED_ARGS == 0,
arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)],
arg_t<Context, NUM_ARGS>[max_of<size_t>(1, NUM_ARGS)],
named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;
type args;
};
@ -2622,22 +2682,17 @@ class context {
private:
appender out_;
format_args args_;
FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_;
FMT_NO_UNIQUE_ADDRESS locale_ref loc_;
public:
/// The character type for the output.
using char_type = char;
using char_type = char; ///< The character type for the output.
using iterator = appender;
using format_arg = basic_format_arg<context>;
using parse_context_type FMT_DEPRECATED = parse_context<>;
template <typename T> using formatter_type FMT_DEPRECATED = formatter<T>;
enum { builtin_types = FMT_BUILTIN_TYPES };
/// Constructs a `context` object. References to the arguments are stored
/// in the object so make sure they have appropriate lifetimes.
FMT_CONSTEXPR context(iterator out, format_args args,
detail::locale_ref loc = {})
FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {})
: out_(out), args_(args), loc_(loc) {}
context(context&&) = default;
context(const context&) = delete;
@ -2650,6 +2705,7 @@ class context {
FMT_CONSTEXPR auto arg_id(string_view name) const -> int {
return args_.get_id(name);
}
auto args() const -> const format_args& { return args_; }
// Returns an iterator to the beginning of the output range.
FMT_CONSTEXPR auto out() const -> iterator { return out_; }
@ -2657,7 +2713,7 @@ class context {
// Advances the begin iterator to `it`.
FMT_CONSTEXPR void advance_to(iterator) {}
FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; }
FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; }
};
template <typename Char = char> struct runtime_format_string {
@ -2695,10 +2751,12 @@ template <typename... T> struct fstring {
template <size_t N>
FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) {
using namespace detail;
static_assert(count<(std::is_base_of<view, remove_reference_t<T>>::value &&
static_assert(count<(is_view<remove_cvref_t<T>>::value &&
std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed");
if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
#if FMT_USE_CONSTEVAL
parse_format_string<char>(s, checker(s, arg_pack()));
#endif
#ifdef FMT_ENFORCE_COMPILE_STRING
static_assert(
FMT_USE_CONSTEVAL && sizeof(s) != 0,
@ -2722,9 +2780,9 @@ template <typename... T> struct fstring {
std::is_same<typename S::char_type, char>::value)>
FMT_ALWAYS_INLINE fstring(const S&) : str(S()) {
FMT_CONSTEXPR auto sv = string_view(S());
FMT_CONSTEXPR int ignore =
FMT_CONSTEXPR int unused =
(parse_format_string(sv, checker(sv, arg_pack())), 0);
detail::ignore_unused(ignore);
detail::ignore_unused(unused);
}
fstring(runtime_format_string<> fmt) : str(fmt.str) {}
@ -2744,9 +2802,6 @@ template <typename T, typename Char = char>
concept formattable = is_formattable<remove_reference_t<T>, Char>::value;
#endif
template <typename T, typename Char>
using has_formatter FMT_DEPRECATED = std::is_constructible<formatter<T, Char>>;
// A formatter specialization for natively supported types.
template <typename T, typename Char>
struct formatter<T, Char,
@ -2943,9 +2998,10 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
return fmt::println(stdout, fmt, static_cast<T&&>(args)...);
}
FMT_END_EXPORT
FMT_PRAGMA_GCC(diagnostic pop)
FMT_PRAGMA_CLANG(diagnostic pop)
FMT_PRAGMA_GCC(pop_options)
FMT_END_EXPORT
FMT_END_NAMESPACE
#ifdef FMT_HEADER_ONLY

View File

@ -22,21 +22,6 @@
#include "format.h"
namespace fmt_detail {
struct time_zone {
template <typename Duration, typename T>
auto to_sys(T)
-> std::chrono::time_point<std::chrono::system_clock, Duration> {
return {};
}
};
template <typename... T> inline auto current_zone(T...) -> time_zone* {
return nullptr;
}
template <typename... T> inline void _tzset(T...) {}
} // namespace fmt_detail
FMT_BEGIN_NAMESPACE
// Enable safe chrono durations, unless explicitly disabled.
@ -53,6 +38,7 @@ FMT_BEGIN_NAMESPACE
// Copyright Paul Dreik 2019
namespace safe_duration_cast {
// DEPRECATED!
template <typename To, typename From,
FMT_ENABLE_IF(!std::is_same<From, To>::value &&
std::numeric_limits<From>::is_signed ==
@ -176,17 +162,6 @@ auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
int& ec) -> To {
using From = std::chrono::duration<FromRep, FromPeriod>;
ec = 0;
if (std::isnan(from.count())) {
// nan in, gives nan out. easy.
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
}
// maybe we should also check if from is denormal, and decide what to do about
// it.
// +-inf should be preserved.
if (std::isinf(from.count())) {
return To{from.count()};
}
// the basic idea is that we need to convert from count() in the from type
// to count() in the To type, by multiplying it with this:
@ -261,7 +236,7 @@ namespace detail {
using utc_clock = std::chrono::utc_clock;
#else
struct utc_clock {
void to_sys();
template <typename T> void to_sys(T);
};
#endif
@ -297,8 +272,6 @@ namespace detail {
#define FMT_NOMACRO
template <typename T = void> struct null {};
inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
inline auto localtime_s(...) -> null<> { return null<>(); }
inline auto gmtime_r(...) -> null<> { return null<>(); }
inline auto gmtime_s(...) -> null<> { return null<>(); }
@ -341,7 +314,7 @@ inline auto get_classic_locale() -> const std::locale& {
}
template <typename CodeUnit> struct codecvt_result {
static constexpr const size_t max_size = 32;
static constexpr size_t max_size = 32;
CodeUnit buf[max_size];
CodeUnit* end;
};
@ -364,7 +337,7 @@ void write_codecvt(codecvt_result<CodeUnit>& out, string_view in,
template <typename OutputIt>
auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)
-> OutputIt {
if (detail::use_utf8 && loc != get_classic_locale()) {
if (const_check(detail::use_utf8) && loc != get_classic_locale()) {
// char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
// gcc-4.
#if FMT_MSC_VERSION != 0 || \
@ -435,14 +408,11 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc,
return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
}
template <typename Rep1, typename Rep2>
struct is_same_arithmetic_type
: public std::integral_constant<bool,
(std::is_integral<Rep1>::value &&
std::is_integral<Rep2>::value) ||
(std::is_floating_point<Rep1>::value &&
std::is_floating_point<Rep2>::value)> {
};
template <typename T, typename U>
using is_similar_arithmetic_type =
bool_constant<(std::is_integral<T>::value && std::is_integral<U>::value) ||
(std::is_floating_point<T>::value &&
std::is_floating_point<U>::value)>;
FMT_NORETURN inline void throw_duration_error() {
FMT_THROW(format_error("cannot format duration"));
@ -461,11 +431,7 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
using common_rep = typename std::common_type<FromRep, typename To::rep,
decltype(factor::num)>::type;
int ec = 0;
auto count = safe_duration_cast::lossless_integral_conversion<common_rep>(
from.count(), ec);
if (ec) throw_duration_error();
common_rep count = from.count(); // This conversion is lossless.
// Multiply from.count() by factor and check for overflow.
if (const_check(factor::num != 1)) {
@ -476,6 +442,7 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
count *= factor::num;
}
if (const_check(factor::den != 1)) count /= factor::den;
int ec = 0;
auto to =
To(safe_duration_cast::lossless_integral_conversion<typename To::rep>(
count, ec));
@ -489,6 +456,8 @@ template <typename To, typename FromRep, typename FromPeriod,
std::is_floating_point<typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#if FMT_SAFE_DURATION_CAST
// Preserve infinity and NaN.
if (!isfinite(from.count())) return static_cast<To>(from.count());
// Throwing version of safe_duration_cast is only available for
// integer to integer or float to float casts.
int ec;
@ -501,11 +470,11 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#endif
}
template <
typename To, typename FromRep, typename FromPeriod,
FMT_ENABLE_IF(!is_same_arithmetic_type<FromRep, typename To::rep>::value)>
template <typename To, typename FromRep, typename FromPeriod,
FMT_ENABLE_IF(
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
// Mixed integer <-> float cast is not supported by safe_duration_cast.
// Mixed integer <-> float cast is not supported by safe duration_cast.
return std::chrono::duration_cast<To>(from);
}
@ -519,68 +488,10 @@ auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
.count();
}
// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without
// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160.
template <typename T> FMT_CONSTEXPR auto has_current_zone() -> bool {
using namespace std::chrono;
using namespace fmt_detail;
return !std::is_same<decltype(current_zone()), fmt_detail::time_zone*>::value;
}
} // namespace detail
FMT_BEGIN_EXPORT
/**
* Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in local time. Unlike `std::localtime`, this function is
* thread-safe on most platforms.
*/
inline auto localtime(std::time_t time) -> std::tm {
struct dispatcher {
std::time_t time_;
std::tm tm_;
inline dispatcher(std::time_t t) : time_(t) {}
inline auto run() -> bool {
using namespace fmt::detail;
return handle(localtime_r(&time_, &tm_));
}
inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }
inline auto handle(detail::null<>) -> bool {
using namespace fmt::detail;
return fallback(localtime_s(&tm_, &time_));
}
inline auto fallback(int res) -> bool { return res == 0; }
#if !FMT_MSC_VERSION
inline auto fallback(detail::null<>) -> bool {
using namespace fmt::detail;
std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
}
#endif
};
dispatcher lt(time);
// Too big time values may be unsupported.
if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
return lt.tm_;
}
#if FMT_USE_LOCAL_TIME
template <typename Duration,
FMT_ENABLE_IF(detail::has_current_zone<Duration>())>
inline auto localtime(std::chrono::local_time<Duration> time) -> std::tm {
using namespace std::chrono;
using namespace fmt_detail;
return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));
}
#endif
/**
* Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this
@ -652,7 +563,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
// Add ASCII '0' to each digit byte and insert separators.
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
constexpr const size_t len = 8;
constexpr size_t len = 8;
if (const_check(is_big_endian())) {
char tmp[len];
std::memcpy(tmp, &digits, len);
@ -911,7 +822,14 @@ template <typename Derived> struct null_chrono_spec_handler {
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
};
struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
class tm_format_checker : public null_chrono_spec_handler<tm_format_checker> {
private:
bool has_timezone_ = false;
public:
constexpr explicit tm_format_checker(bool has_timezone)
: has_timezone_(has_timezone) {}
FMT_NORETURN inline void unsupported() {
FMT_THROW(format_error("no format"));
}
@ -949,8 +867,12 @@ struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
FMT_CONSTEXPR void on_24_hour_time() {}
FMT_CONSTEXPR void on_iso_time() {}
FMT_CONSTEXPR void on_am_pm() {}
FMT_CONSTEXPR void on_utc_offset(numeric_system) {}
FMT_CONSTEXPR void on_tz_name() {}
FMT_CONSTEXPR void on_utc_offset(numeric_system) {
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
}
FMT_CONSTEXPR void on_tz_name() {
if (!has_timezone_) FMT_THROW(format_error("no timezone"));
}
};
inline auto tm_wday_full_name(int wday) -> const char* {
@ -980,24 +902,27 @@ inline auto tm_mon_short_name(int mon) -> const char* {
}
template <typename T, typename = void>
struct has_member_data_tm_gmtoff : std::false_type {};
struct has_tm_gmtoff : std::false_type {};
template <typename T>
struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
: std::true_type {};
struct has_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>> : std::true_type {};
template <typename T, typename = void>
struct has_member_data_tm_zone : std::false_type {};
template <typename T, typename = void> struct has_tm_zone : std::false_type {};
template <typename T>
struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
: std::true_type {};
struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {};
inline void tzset_once() {
static bool init = []() {
using namespace fmt_detail;
_tzset();
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
auto set_tm_zone(T& time, char* tz) -> bool {
time.tm_zone = tz;
return true;
}
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
auto set_tm_zone(T&, char*) -> bool {
return false;
}();
ignore_unused(init);
}
inline auto utc() -> char* {
static char tz[] = "UTC";
return tz;
}
// Converts value to Int and checks that it's in the range [0, upper).
@ -1005,7 +930,7 @@ template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
inline auto to_nonnegative_int(T value, Int upper) -> Int {
if (!std::is_unsigned<Int>::value &&
(value < 0 || to_unsigned(value) > to_unsigned(upper))) {
FMT_THROW(fmt::format_error("chrono value is out of range"));
FMT_THROW(format_error("chrono value is out of range"));
}
return static_cast<Int>(value);
}
@ -1090,7 +1015,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
// Format subseconds which are given as a floating point type with an
// appropriate number of digits. We cannot pass the Duration here, as we
// explicitly need to pass the Rep value in the chrono_formatter.
// explicitly need to pass the Rep value in the duration_formatter.
template <typename Duration>
void write_floating_seconds(memory_buffer& buf, Duration duration,
int num_fractional_digits = -1) {
@ -1124,7 +1049,7 @@ class tm_writer {
static constexpr int days_per_week = 7;
const std::locale& loc_;
const bool is_classic_;
bool is_classic_;
OutputIt out_;
const Duration* subsecs_;
const std::tm& tm_;
@ -1160,8 +1085,8 @@ class tm_writer {
}
auto tm_hour12() const noexcept -> int {
const auto h = tm_hour();
const auto z = h < 12 ? h : h - 12;
auto h = tm_hour();
auto z = h < 12 ? h : h - 12;
return z == 0 ? 12 : z;
}
@ -1177,11 +1102,11 @@ class tm_writer {
// Algorithm: https://en.wikipedia.org/wiki/ISO_week_date.
auto iso_year_weeks(long long curr_year) const noexcept -> int {
const auto prev_year = curr_year - 1;
const auto curr_p =
auto prev_year = curr_year - 1;
auto curr_p =
(curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
days_per_week;
const auto prev_p =
auto prev_p =
(prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
days_per_week;
return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
@ -1191,15 +1116,15 @@ class tm_writer {
days_per_week;
}
auto tm_iso_week_year() const noexcept -> long long {
const auto year = tm_year();
const auto w = iso_week_num(tm_yday(), tm_wday());
auto year = tm_year();
auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return year - 1;
if (w > iso_year_weeks(year)) return year + 1;
return year;
}
auto tm_iso_week_of_year() const noexcept -> int {
const auto year = tm_year();
const auto w = iso_week_num(tm_yday(), tm_wday());
auto year = tm_year();
auto w = iso_week_num(tm_yday(), tm_wday());
if (w < 1) return iso_year_weeks(year - 1);
if (w > iso_year_weeks(year)) return 1;
return w;
@ -1236,9 +1161,8 @@ class tm_writer {
uint32_or_64_or_128_t<long long> n = to_unsigned(year);
const int num_digits = count_digits(n);
if (negative && pad == pad_type::zero) *out_++ = '-';
if (width > num_digits) {
if (width > num_digits)
out_ = detail::write_padding(out_, pad, width - num_digits);
}
if (negative && pad != pad_type::zero) *out_++ = '-';
out_ = format_decimal<Char>(out_, n, num_digits);
}
@ -1259,45 +1183,22 @@ class tm_writer {
write2(static_cast<int>(offset % 60));
}
template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>
void format_utc_offset_impl(const T& tm, numeric_system ns) {
template <typename T, FMT_ENABLE_IF(has_tm_gmtoff<T>::value)>
void format_utc_offset(const T& tm, numeric_system ns) {
write_utc_offset(tm.tm_gmtoff, ns);
}
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>
void format_utc_offset_impl(const T& tm, numeric_system ns) {
#if defined(_WIN32) && defined(_UCRT)
tzset_once();
long offset = 0;
_get_timezone(&offset);
if (tm.tm_isdst) {
long dstbias = 0;
_get_dstbias(&dstbias);
offset += dstbias;
}
write_utc_offset(-offset, ns);
#else
if (ns == numeric_system::standard) return format_localized('z');
// Extract timezone offset from timezone conversion functions.
std::tm gtm = tm;
std::time_t gt = std::mktime(&gtm);
std::tm ltm = gmtime(gt);
std::time_t lt = std::mktime(&ltm);
long long offset = gt - lt;
write_utc_offset(offset, ns);
#endif
template <typename T, FMT_ENABLE_IF(!has_tm_gmtoff<T>::value)>
void format_utc_offset(const T&, numeric_system ns) {
write_utc_offset(0, ns);
}
template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>
void format_tz_name_impl(const T& tm) {
if (is_classic_)
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
void format_tz_name(const T& tm) {
out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
else
format_localized('Z');
}
template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>
void format_tz_name_impl(const T&) {
format_localized('Z');
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
void format_tz_name(const T&) {
out_ = std::copy_n(utc(), 3, out_);
}
void format_localized(char format, char modifier = 0) {
@ -1408,8 +1309,8 @@ class tm_writer {
out_ = copy<Char>(std::begin(buf) + offset, std::end(buf), out_);
}
void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); }
void on_tz_name() { format_tz_name_impl(tm_); }
void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); }
void on_tz_name() { format_tz_name(tm_); }
void on_year(numeric_system ns, pad_type pad) {
if (is_classic_ || ns == numeric_system::standard)
@ -1483,11 +1384,10 @@ class tm_writer {
void on_day_of_year(pad_type pad) {
auto yday = tm_yday() + 1;
auto digit1 = yday / 100;
if (digit1 != 0) {
if (digit1 != 0)
write1(digit1);
} else {
else
out_ = detail::write_padding(out_, pad);
}
write2(yday % 100, pad);
}
@ -1624,18 +1524,16 @@ template <typename Rep, typename Period,
FMT_ENABLE_IF(std::is_integral<Rep>::value)>
inline auto get_milliseconds(std::chrono::duration<Rep, Period> d)
-> std::chrono::duration<Rep, std::milli> {
// this may overflow and/or the result may not fit in the
// target type.
// This may overflow and/or the result may not fit in the target type.
#if FMT_SAFE_DURATION_CAST
using CommonSecondsType =
using common_seconds_type =
typename std::common_type<decltype(d), std::chrono::seconds>::type;
const auto d_as_common = detail::duration_cast<CommonSecondsType>(d);
const auto d_as_whole_seconds =
auto d_as_common = detail::duration_cast<common_seconds_type>(d);
auto d_as_whole_seconds =
detail::duration_cast<std::chrono::seconds>(d_as_common);
// this conversion should be nonproblematic
const auto diff = d_as_common - d_as_whole_seconds;
const auto ms =
detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
// This conversion should be nonproblematic.
auto diff = d_as_common - d_as_whole_seconds;
auto ms = detail::duration_cast<std::chrono::duration<Rep, std::milli>>(diff);
return ms;
#else
auto s = detail::duration_cast<std::chrono::seconds>(d);
@ -1696,8 +1594,13 @@ class get_locale {
public:
inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
if (localized)
::new (&locale_) std::locale(loc.template get<std::locale>());
if (!localized) return;
ignore_unused(loc);
::new (&locale_) std::locale(
#if FMT_USE_LOCALE
loc.template get<std::locale>()
#endif
);
}
inline ~get_locale() {
if (has_locale_) locale_.~locale();
@ -1707,32 +1610,28 @@ class get_locale {
}
};
template <typename FormatContext, typename OutputIt, typename Rep,
typename Period>
struct chrono_formatter {
FormatContext& context;
OutputIt out;
int precision;
bool localized = false;
template <typename Char, typename Rep, typename Period>
struct duration_formatter {
using iterator = basic_appender<Char>;
iterator out;
// rep is unsigned to avoid overflow.
using rep =
conditional_t<std::is_integral<Rep>::value && sizeof(Rep) < sizeof(int),
unsigned, typename make_unsigned_or_unchanged<Rep>::type>;
rep val;
int precision;
locale_ref locale;
bool localized = false;
using seconds = std::chrono::duration<rep>;
seconds s;
using milliseconds = std::chrono::duration<rep, std::milli>;
bool negative;
using char_type = typename FormatContext::char_type;
using tm_writer_type = tm_writer<OutputIt, char_type>;
using tm_writer_type = tm_writer<iterator, Char>;
chrono_formatter(FormatContext& ctx, OutputIt o,
std::chrono::duration<Rep, Period> d)
: context(ctx),
out(o),
val(static_cast<rep>(d.count())),
negative(false) {
duration_formatter(iterator o, std::chrono::duration<Rep, Period> d,
locale_ref loc)
: out(o), val(static_cast<rep>(d.count())), locale(loc), negative(false) {
if (d.count() < 0) {
val = 0 - val;
negative = true;
@ -1746,19 +1645,16 @@ struct chrono_formatter {
// returns true if nan or inf, writes to out.
auto handle_nan_inf() -> bool {
if (isfinite(val)) {
return false;
}
if (isfinite(val)) return false;
if (isnan(val)) {
write_nan();
return true;
}
// must be +-inf
if (val > 0) {
write_pinf();
} else {
write_ninf();
}
if (val > 0)
std::copy_n("inf", 3, out);
else
std::copy_n("-inf", 4, out);
return true;
}
@ -1786,11 +1682,10 @@ struct chrono_formatter {
}
void write_sign() {
if (negative) {
if (!negative) return;
*out++ = '-';
negative = false;
}
}
void write(Rep value, int width, pad_type pad = pad_type::zero) {
write_sign();
@ -1801,24 +1696,22 @@ struct chrono_formatter {
if (width > num_digits) {
out = detail::write_padding(out, pad, width - num_digits);
}
out = format_decimal<char_type>(out, n, num_digits);
out = format_decimal<Char>(out, n, num_digits);
}
void write_nan() { std::copy_n("nan", 3, out); }
void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); }
template <typename Callback, typename... Args>
void format_tm(const tm& time, Callback cb, Args... args) {
if (isnan(val)) return write_nan();
get_locale loc(localized, context.locale());
get_locale loc(localized, locale);
auto w = tm_writer_type(loc, out, time);
(w.*cb)(args...);
out = w.out();
}
void on_text(const char_type* begin, const char_type* end) {
copy<char_type>(begin, end, out);
void on_text(const Char* begin, const Char* end) {
copy<Char>(begin, end, out);
}
// These are not implemented because durations don't have date information.
@ -1888,13 +1781,12 @@ struct chrono_formatter {
write_floating_seconds(buf, std::chrono::duration<rep, Period>(val),
precision);
if (negative) *out++ = '-';
if (buf.size() < 2 || buf[1] == '.') {
if (buf.size() < 2 || buf[1] == '.')
out = detail::write_padding(out, pad);
}
out = copy<char_type>(buf.begin(), buf.end(), out);
out = copy<Char>(buf.begin(), buf.end(), out);
} else {
write(second(), 2, pad);
write_fractional_seconds<char_type>(
write_fractional_seconds<Char>(
out, std::chrono::duration<rep, Period>(val), precision);
}
return;
@ -1936,12 +1828,10 @@ struct chrono_formatter {
void on_duration_value() {
if (handle_nan_inf()) return;
write_sign();
out = format_duration_value<char_type>(out, val, precision);
out = format_duration_value<Char>(out, val, precision);
}
void on_duration_unit() {
out = format_duration_unit<char_type, Period>(out);
}
void on_duration_unit() { out = format_duration_unit<Char, Period>(out); }
};
} // namespace detail
@ -2011,12 +1901,11 @@ class year_month_day {
constexpr auto month() const noexcept -> fmt::month { return month_; }
constexpr auto day() const noexcept -> fmt::day { return day_; }
};
#endif
#endif // __cpp_lib_chrono >= 201907
template <typename Char>
struct formatter<weekday, Char> : private formatter<std::tm, Char> {
private:
bool localized_ = false;
bool use_tm_formatter_ = false;
public:
@ -2024,8 +1913,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == 'L') {
++it;
localized_ = true;
return it;
this->set_localized();
}
use_tm_formatter_ = it != end && *it != '}';
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@ -2036,7 +1924,7 @@ struct formatter<weekday, Char> : private formatter<std::tm, Char> {
auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding());
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
detail::get_locale loc(localized_, ctx.locale());
detail::get_locale loc(this->localized(), ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_weekday();
return w.out();
@ -2070,7 +1958,6 @@ struct formatter<day, Char> : private formatter<std::tm, Char> {
template <typename Char>
struct formatter<month, Char> : private formatter<std::tm, Char> {
private:
bool localized_ = false;
bool use_tm_formatter_ = false;
public:
@ -2078,8 +1965,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
auto it = ctx.begin(), end = ctx.end();
if (it != end && *it == 'L') {
++it;
localized_ = true;
return it;
this->set_localized();
}
use_tm_formatter_ = it != end && *it != '}';
return use_tm_formatter_ ? formatter<std::tm, Char>::parse(ctx) : it;
@ -2090,7 +1976,7 @@ struct formatter<month, Char> : private formatter<std::tm, Char> {
auto time = std::tm();
time.tm_mon = static_cast<int>(static_cast<unsigned>(m)) - 1;
if (use_tm_formatter_) return formatter<std::tm, Char>::format(time, ctx);
detail::get_locale loc(localized_, ctx.locale());
detail::get_locale loc(this->localized(), ctx.locale());
auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
w.on_abbr_month();
return w.out();
@ -2154,7 +2040,6 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
format_specs specs_;
detail::arg_ref<Char> width_ref_;
detail::arg_ref<Char> precision_ref_;
bool localized_ = false;
basic_string_view<Char> fmt_;
public:
@ -2177,7 +2062,7 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
it = detail::parse_precision(it, end, specs_, precision_ref_, ctx);
}
if (it != end && *it == 'L') {
localized_ = true;
specs_.set_localized();
++it;
}
end = detail::parse_chrono_format(it, end, checker);
@ -2204,11 +2089,10 @@ struct formatter<std::chrono::duration<Rep, Period>, Char> {
out = detail::format_duration_value<Char>(out, d.count(), precision);
detail::format_duration_unit<Char, Period>(out);
} else {
using chrono_formatter =
detail::chrono_formatter<FormatContext, decltype(out), Rep, Period>;
auto f = chrono_formatter(ctx, out, d);
auto f =
detail::duration_formatter<Char, Rep, Period>(out, d, ctx.locale());
f.precision = precision;
f.localized = localized_;
f.localized = specs_.localized();
detail::parse_chrono_format(begin, end, f);
}
return detail::write(
@ -2220,30 +2104,15 @@ template <typename Char> struct formatter<std::tm, Char> {
private:
format_specs specs_;
detail::arg_ref<Char> width_ref_;
basic_string_view<Char> fmt_ =
detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
protected:
basic_string_view<Char> fmt_;
auto localized() const -> bool { return specs_.localized(); }
FMT_CONSTEXPR void set_localized() { specs_.set_localized(); }
template <typename Duration, typename FormatContext>
auto do_format(const std::tm& tm, FormatContext& ctx,
const Duration* subsecs) const -> decltype(ctx.out()) {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(buf);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto loc_ref = ctx.locale();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w =
detail::tm_writer<decltype(out), Char, Duration>(loc, out, tm, subsecs);
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, bool has_timezone)
-> const Char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end || *it == '}') return it;
@ -2256,12 +2125,41 @@ template <typename Char> struct formatter<std::tm, Char> {
if (it == end) return it;
}
end = detail::parse_chrono_format(it, end, detail::tm_format_checker());
if (*it == 'L') {
specs_.set_localized();
++it;
}
end = detail::parse_chrono_format(it, end,
detail::tm_format_checker(has_timezone));
// Replace the default format string only if the new spec is not empty.
if (end != it) fmt_ = {it, detail::to_unsigned(end - it)};
return end;
}
template <typename Duration, typename FormatContext>
auto do_format(const std::tm& tm, FormatContext& ctx,
const Duration* subsecs) const -> decltype(ctx.out()) {
auto specs = specs_;
auto buf = basic_memory_buffer<Char>();
auto out = basic_appender<Char>(buf);
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
auto loc_ref = specs.localized() ? ctx.locale() : locale_ref();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
loc, out, tm, subsecs);
detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w);
return detail::write(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, detail::has_tm_gmtoff<std::tm>::value);
}
template <typename FormatContext>
auto format(const std::tm& tm, FormatContext& ctx) const
-> decltype(ctx.out()) {
@ -2269,10 +2167,11 @@ template <typename Char> struct formatter<std::tm, Char> {
}
};
// DEPRECATED! Reversed order of template parameters.
template <typename Char, typename Duration>
struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
FMT_CONSTEXPR formatter() {
this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
struct formatter<sys_time<Duration>, Char> : private formatter<std::tm, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return this->do_parse(ctx, true);
}
template <typename FormatContext>
@ -2283,6 +2182,7 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
if (detail::const_check(
period::num == 1 && period::den == 1 &&
!std::is_floating_point<typename Duration::rep>::value)) {
detail::set_tm_zone(tm, detail::utc());
return formatter<std::tm, Char>::format(tm, ctx);
}
Duration epoch = val.time_since_epoch();
@ -2290,11 +2190,13 @@ struct formatter<sys_time<Duration>, Char> : formatter<std::tm, Char> {
epoch - detail::duration_cast<std::chrono::seconds>(epoch));
if (subsecs.count() < 0) {
auto second = detail::duration_cast<Duration>(std::chrono::seconds(1));
if (tm.tm_sec != 0)
if (tm.tm_sec != 0) {
--tm.tm_sec;
else
} else {
tm = gmtime(val - second);
subsecs += detail::duration_cast<Duration>(std::chrono::seconds(1));
detail::set_tm_zone(tm, detail::utc());
}
subsecs += second;
}
return formatter<std::tm, Char>::do_format(tm, ctx, &subsecs);
}
@ -2312,23 +2214,29 @@ struct formatter<utc_time<Duration>, Char>
};
template <typename Duration, typename Char>
struct formatter<local_time<Duration>, Char> : formatter<std::tm, Char> {
FMT_CONSTEXPR formatter() {
this->fmt_ = detail::string_literal<Char, '%', 'F', ' ', '%', 'T'>();
struct formatter<local_time<Duration>, Char>
: private formatter<std::tm, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return this->do_parse(ctx, false);
}
template <typename FormatContext>
auto format(local_time<Duration> val, FormatContext& ctx) const
-> decltype(ctx.out()) {
auto time_since_epoch = val.time_since_epoch();
auto seconds_since_epoch =
detail::duration_cast<std::chrono::seconds>(time_since_epoch);
// Use gmtime to prevent time zone conversion since local_time has an
// unspecified time zone.
std::tm t = gmtime(seconds_since_epoch.count());
using period = typename Duration::period;
if (period::num == 1 && period::den == 1 &&
!std::is_floating_point<typename Duration::rep>::value) {
return formatter<std::tm, Char>::format(localtime(val), ctx);
return formatter<std::tm, Char>::format(t, ctx);
}
auto epoch = val.time_since_epoch();
auto subsecs = detail::duration_cast<Duration>(
epoch - detail::duration_cast<std::chrono::seconds>(epoch));
return formatter<std::tm, Char>::do_format(localtime(val), ctx, &subsecs);
auto subsecs =
detail::duration_cast<Duration>(time_since_epoch - seconds_since_epoch);
return formatter<std::tm, Char>::do_format(t, ctx, &subsecs);
}
};

View File

@ -190,11 +190,11 @@ enum class emphasis : uint8_t {
// rgb is a struct for red, green and blue colors.
// Using the name "rgb" makes some editors show the color in a tooltip.
struct rgb {
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
FMT_CONSTEXPR rgb(uint32_t hex)
constexpr rgb() : r(0), g(0), b(0) {}
constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
constexpr rgb(uint32_t hex)
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
FMT_CONSTEXPR rgb(color hex)
constexpr rgb(color hex)
: r((uint32_t(hex) >> 16) & 0xFF),
g((uint32_t(hex) >> 8) & 0xFF),
b(uint32_t(hex) & 0xFF) {}
@ -205,97 +205,135 @@ struct rgb {
namespace detail {
// color is a struct of either a rgb color or a terminal color.
// A bit-packed variant of an RGB color, a terminal color, or unset color.
// see text_style for the bit-packing scheme.
struct color_type {
FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {}
FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = static_cast<uint32_t>(rgb_color);
constexpr color_type() noexcept = default;
constexpr color_type(color rgb_color) noexcept
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
constexpr color_type(rgb rgb_color) noexcept
: color_type(static_cast<color>(
(static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
constexpr color_type(terminal_color term_color) noexcept
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
constexpr auto is_terminal_color() const noexcept -> bool {
return (value_ & (1 << 25)) != 0;
}
FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} {
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
constexpr auto value() const noexcept -> uint32_t {
return value_ & 0xFFFFFF;
}
FMT_CONSTEXPR color_type(terminal_color term_color) noexcept
: is_rgb(), value{} {
value.term_color = static_cast<uint8_t>(term_color);
}
bool is_rgb;
union color_union {
uint8_t term_color;
uint32_t rgb_color;
} value;
constexpr color_type(uint32_t value) noexcept : value_(value) {}
uint32_t value_ = 0;
};
} // namespace detail
/// A text style consisting of foreground and background colors and emphasis.
class text_style {
// The information is packed as follows:
// ┌──┐
// │ 0│─┐
// │..│ ├── foreground color value
// │23│─┘
// ├──┤
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
// ├──┤
// │26│──── overflow bit, always zero (see below)
// ├──┤
// │27│─┐
// │..│ │
// │50│ │
// ├──┤ │
// │51│ ├── background color (same format as the foreground color)
// │52│ │
// ├──┤ │
// │53│─┘
// ├──┤
// │54│─┐
// │..│ ├── emphases
// │61│─┘
// ├──┤
// │62│─┬── unused
// │63│─┘
// └──┘
// The overflow bits are there to make operator|= efficient.
// When ORing, we must throw if, for either the foreground or background,
// one style specifies a terminal color and the other specifies any color
// (terminal or RGB); in other words, if one discriminator is 11 and the
// other is 11 or 01.
//
// We do that check by adding the styles. Consider what adding does to each
// possible pair of discriminators:
// 00 + 00 = 000
// 01 + 00 = 001
// 11 + 00 = 011
// 01 + 01 = 010
// 11 + 01 = 100 (!!)
// 11 + 11 = 110 (!!)
// In the last two cases, the ones we want to catch, the third bit——the
// overflow bit——is set. Bingo.
//
// We must take into account the possible carry bit from the bits
// before the discriminator. The only potentially problematic case is
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
// bit is impossible in that case, because 00 (unset color) means the
// 24 bits that precede the discriminator are all zero.
//
// This test can be applied to both colors simultaneously.
public:
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
: set_foreground_color(), set_background_color(), ems(em) {}
: style_(static_cast<uint64_t>(em) << 54) {}
FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& {
if (!set_foreground_color) {
set_foreground_color = rhs.set_foreground_color;
foreground_color = rhs.foreground_color;
} else if (rhs.set_foreground_color) {
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
report_error("can't OR a terminal color");
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
}
if (!set_background_color) {
set_background_color = rhs.set_background_color;
background_color = rhs.background_color;
} else if (rhs.set_background_color) {
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
report_error("can't OR a terminal color");
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
}
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
static_cast<uint8_t>(rhs.ems));
style_ |= rhs.style_;
return *this;
}
friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs)
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
-> text_style {
return lhs |= rhs;
}
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
return style_ == rhs.style_;
}
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
return !(*this == rhs);
}
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
return set_foreground_color;
return (style_ & (1 << 24)) != 0;
}
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
return set_background_color;
return (style_ & (1ULL << 51)) != 0;
}
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
return static_cast<uint8_t>(ems) != 0;
return (style_ >> 54) != 0;
}
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
return foreground_color;
return style_ & 0x3FFFFFF;
}
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
FMT_ASSERT(has_background(), "no background specified for this style");
return background_color;
return (style_ >> 27) & 0x3FFFFFF;
}
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
return ems;
return static_cast<emphasis>(style_ >> 54);
}
private:
FMT_CONSTEXPR text_style(bool is_foreground,
detail::color_type text_color) noexcept
: set_foreground_color(), set_background_color(), ems() {
if (is_foreground) {
foreground_color = text_color;
set_foreground_color = true;
} else {
background_color = text_color;
set_background_color = true;
}
}
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
-> text_style;
@ -303,23 +341,19 @@ class text_style {
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
-> text_style;
detail::color_type foreground_color;
detail::color_type background_color;
bool set_foreground_color;
bool set_background_color;
emphasis ems;
uint64_t style_ = 0;
};
/// Creates a text style from the foreground (text) color.
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
-> text_style {
return text_style(true, foreground);
return foreground.value_;
}
/// Creates a text style from the background color.
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
-> text_style {
return text_style(false, background);
return static_cast<uint64_t>(background.value_) << 27;
}
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
@ -334,37 +368,35 @@ template <typename Char> struct ansi_color_escape {
const char* esc) noexcept {
// If we have a terminal color, we need to output another escape code
// sequence.
if (!text_color.is_rgb) {
if (text_color.is_terminal_color()) {
bool is_background = esc == string_view("\x1b[48;2;");
uint32_t value = text_color.value.term_color;
uint32_t value = text_color.value();
// Background ASCII codes are the same as the foreground ones but with
// 10 more.
if (is_background) value += 10u;
size_t index = 0;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
if (value >= 100u) {
buffer[index++] = static_cast<Char>('1');
buffer[size++] = static_cast<Char>('1');
value %= 100u;
}
buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[size++] = static_cast<Char>('0' + value / 10u);
buffer[size++] = static_cast<Char>('0' + value % 10u);
buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
buffer[size++] = static_cast<Char>('m');
return;
}
for (int i = 0; i < 7; i++) {
buffer[i] = static_cast<Char>(esc[i]);
}
rgb color(text_color.value.rgb_color);
rgb color(text_color.value());
to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm');
buffer[19] = static_cast<Char>(0);
size = 19;
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {};
@ -377,26 +409,28 @@ template <typename Char> struct ansi_color_escape {
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0;
buffer[size++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
buffer[size++] = static_cast<Char>('0' + em_codes[i]);
buffer[size++] = static_cast<Char>(';');
}
buffer[index++] = static_cast<Char>(0);
buffer[size - 1] = static_cast<Char>('m');
}
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
return buffer + basic_string_view<Char>(buffer).size();
FMT_CONSTEXPR auto end() const noexcept -> const Char* {
return buffer + size;
}
private:
static constexpr size_t num_emphases = 8;
Char buffer[7u + 3u * num_emphases + 1u];
Char buffer[7u + 4u * num_emphases] = {};
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept {
@ -441,32 +475,26 @@ template <typename T> struct styled_arg : view {
};
template <typename Char>
void vformat_to(buffer<Char>& buf, const text_style& ts,
basic_string_view<Char> fmt,
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) {
bool has_style = false;
if (ts.has_emphasis()) {
has_style = true;
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
buf.append(emphasis.begin(), emphasis.end());
}
if (ts.has_foreground()) {
has_style = true;
auto foreground = make_foreground_color<Char>(ts.get_foreground());
buf.append(foreground.begin(), foreground.end());
}
if (ts.has_background()) {
has_style = true;
auto background = make_background_color<Char>(ts.get_background());
buf.append(background.begin(), background.end());
}
vformat_to(buf, fmt, args);
if (has_style) reset_color<Char>(buf);
if (ts != text_style()) reset_color<Char>(buf);
}
} // namespace detail
inline void vprint(FILE* f, const text_style& ts, string_view fmt,
format_args args) {
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
@ -482,8 +510,7 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt,
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(FILE* f, const text_style& ts, format_string<T...> fmt,
T&&... args) {
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
}
@ -497,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string<T...> fmt,
* "Elapsed time: {0:.2f} seconds", 1.23);
*/
template <typename... T>
void print(const text_style& ts, format_string<T...> fmt, T&&... args) {
void print(text_style ts, format_string<T...> fmt, T&&... args) {
return print(stdout, ts, fmt, std::forward<T>(args)...);
}
inline auto vformat(const text_style& ts, string_view fmt, format_args args)
inline auto vformat(text_style ts, string_view fmt, format_args args)
-> std::string {
auto buf = memory_buffer();
detail::vformat_to(buf, ts, fmt, args);
@ -521,7 +548,7 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args)
* ```
*/
template <typename... T>
inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
-> std::string {
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
}
@ -529,8 +556,8 @@ inline auto format(const text_style& ts, format_string<T...> fmt, T&&... args)
/// Formats a string with the given text_style and writes the output to `out`.
template <typename OutputIt,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
format_args args) -> OutputIt {
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
-> OutputIt {
auto&& buf = detail::get_buffer<char>(out);
detail::vformat_to(buf, ts, fmt, args);
return detail::get_iterator(buf, out);
@ -548,8 +575,8 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt,
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
inline auto format_to(OutputIt out, const text_style& ts,
format_string<T...> fmt, T&&... args) -> OutputIt {
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
T&&... args) -> OutputIt {
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
}

View File

@ -15,11 +15,10 @@
#include "format.h"
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
// A compile-time string which is compiled into fast formatting code.
FMT_EXPORT class compiled_string {};
namespace detail {
class compiled_string {};
template <typename S>
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
@ -41,28 +40,42 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
# define FMT_COMPILE(s) FMT_STRING(s)
#endif
/**
* Converts a string literal into a format string that will be parsed at
* compile time and converted into efficient formatting code. Requires support
* for class types in constant template parameters (a C++20 feature).
*
* **Example**:
*
* // Converts 42 into std::string using the most efficient method and no
* // runtime format string processing.
* using namespace fmt::literals;
* std::string s = fmt::format("{}"_cf, 42);
*/
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N, fmt::detail::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string {
using char_type = Char;
constexpr explicit operator basic_string_view<char_type>() const {
return {Str.data, N - 1};
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
return FMT_COMPILE(Str.data);
}
};
} // namespace literals
#endif
FMT_END_EXPORT
namespace detail {
template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& {
constexpr auto first(const T& value, const Tail&...) -> const T& {
return value;
}
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {};
template <typename... T> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
template <int N, typename T, typename... Args>
constexpr const auto& get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) {
constexpr auto get([[maybe_unused]] const T& first,
[[maybe_unused]] const Args&... rest) -> const auto& {
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
if constexpr (N == 0)
return first;
@ -94,8 +107,8 @@ FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
}
template <typename Char, typename... Args>
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) {
constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
type_list<Args...>) -> int {
return get_arg_index_by_name<Args...>(name);
}
@ -115,8 +128,8 @@ template <typename Char> struct text {
basic_string_view<Char> data;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
return write<Char>(out, data);
}
};
@ -125,8 +138,8 @@ template <typename Char>
struct is_compiled_format<text<Char>> : std::true_type {};
template <typename Char>
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
size_t size) {
constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
-> text<Char> {
return {{&s[pos], size}};
}
@ -134,8 +147,8 @@ template <typename Char> struct code_unit {
Char value;
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&...) const {
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
*out++ = value;
return out;
}
@ -143,7 +156,7 @@ template <typename Char> struct code_unit {
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
constexpr auto get_arg_checked(const Args&... args) -> const T& {
const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
@ -156,13 +169,13 @@ template <typename Char>
struct is_compiled_format<code_unit<Char>> : std::true_type {};
// A replacement field that refers to argument N.
template <typename Char, typename T, int N> struct field {
template <typename Char, typename V, int N> struct field {
using char_type = Char;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
const T& arg = get_arg_checked<T, N>(args...);
if constexpr (std::is_convertible<T, basic_string_view<Char>>::value) {
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
const V& arg = get_arg_checked<V, N>(args...);
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
auto s = basic_string_view<Char>(arg);
return copy<Char>(s.begin(), s.end(), out);
} else {
@ -180,10 +193,10 @@ template <typename Char> struct runtime_named_field {
basic_string_view<Char> name;
template <typename OutputIt, typename T>
constexpr static bool try_format_argument(
constexpr static auto try_format_argument(
OutputIt& out,
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
if (arg_name == arg.name) {
out = write<Char>(out, arg.value);
@ -193,8 +206,8 @@ template <typename Char> struct runtime_named_field {
return false;
}
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
FMT_THROW(format_error("argument with specified name is not found"));
@ -207,17 +220,17 @@ template <typename Char>
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
// A replacement field that refers to argument N and has format specifiers.
template <typename Char, typename T, int N> struct spec_field {
template <typename Char, typename V, int N> struct spec_field {
using char_type = Char;
formatter<T, Char> fmt;
formatter<V, Char> fmt;
template <typename OutputIt, typename... Args>
constexpr FMT_INLINE OutputIt format(OutputIt out,
const Args&... args) const {
template <typename OutputIt, typename... T>
constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
-> OutputIt {
const auto& vargs =
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
basic_format_context<OutputIt, Char> ctx(out, vargs);
return fmt.format(get_arg_checked<T, N>(args...), ctx);
return fmt.format(get_arg_checked<V, N>(args...), ctx);
}
};
@ -229,8 +242,8 @@ template <typename L, typename R> struct concat {
R rhs;
using char_type = typename L::char_type;
template <typename OutputIt, typename... Args>
constexpr OutputIt format(OutputIt out, const Args&... args) const {
template <typename OutputIt, typename... T>
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
out = lhs.format(out, args...);
return rhs.format(out, args...);
}
@ -240,14 +253,14 @@ template <typename L, typename R>
struct is_compiled_format<concat<L, R>> : std::true_type {};
template <typename L, typename R>
constexpr concat<L, R> make_concat(L lhs, R rhs) {
constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
return {lhs, rhs};
}
struct unknown_format {};
template <typename Char>
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
for (size_t size = str.size(); pos != size; ++pos) {
if (str[pos] == '{' || str[pos] == '}') break;
}
@ -280,8 +293,8 @@ template <typename T, typename Char> struct parse_specs_result {
enum { manual_indexing_id = -1 };
template <typename T, typename Char>
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
size_t pos, int next_arg_id) {
constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
int next_arg_id) -> parse_specs_result<T, Char> {
str.remove_prefix(pos);
auto ctx =
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
@ -295,16 +308,16 @@ template <typename Char> struct arg_id_handler {
arg_id_kind kind;
arg_ref<Char> arg_id;
constexpr int on_auto() {
constexpr auto on_auto() -> int {
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
return 0;
}
constexpr int on_index(int id) {
constexpr auto on_index(int id) -> int {
kind = arg_id_kind::index;
arg_id = arg_ref<Char>(id);
return 0;
}
constexpr int on_name(basic_string_view<Char> id) {
constexpr auto on_name(basic_string_view<Char> id) -> int {
kind = arg_id_kind::name;
arg_id = arg_ref<Char>(id);
return 0;
@ -425,7 +438,7 @@ constexpr auto compile_format_string(S fmt) {
}
template <typename... Args, typename S,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_ENABLE_IF(is_compiled_string<S>::value)>
constexpr auto compile(S fmt) {
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
if constexpr (str.size() == 0) {
@ -443,27 +456,28 @@ FMT_BEGIN_EXPORT
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args,
template <typename CompiledFormat, typename... T,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
const T&... args)
-> std::basic_string<Char> {
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
}
template <typename OutputIt, typename CompiledFormat, typename... Args,
template <typename OutputIt, typename CompiledFormat, typename... T,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
const Args&... args) {
constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
const T&... args) -> OutputIt {
return cf.format(out, args...);
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
-> std::basic_string<typename S::char_type> {
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
@ -476,74 +490,97 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
}
}
}
constexpr auto compiled = detail::compile<Args...>(S());
constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format(
static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
std::forward<T>(args)...);
} else {
return fmt::format(compiled, std::forward<Args>(args)...);
return fmt::format(compiled, std::forward<T>(args)...);
}
}
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
constexpr auto compiled = detail::compile<Args...>(S());
template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
constexpr auto compiled = detail::compile<T...>(S());
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
detail::unknown_format>()) {
return fmt::format_to(
out, static_cast<basic_string_view<typename S::char_type>>(S()),
std::forward<Args>(args)...);
std::forward<T>(args)...);
} else {
return fmt::format_to(out, compiled, std::forward<Args>(args)...);
return fmt::format_to(out, compiled, std::forward<T>(args)...);
}
}
#endif
template <typename OutputIt, typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args)
template <typename OutputIt, typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(std::back_inserter(buf), fmt, std::forward<Args>(args)...);
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()};
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
-> size_t {
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
auto buf = detail::counting_buffer<>();
fmt::format_to(appender(buf), fmt, args...);
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
return buf.count();
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) {
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, T&&... args) {
auto buf = memory_buffer();
fmt::format_to(appender(buf), fmt, args...);
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
detail::print(f, {buf.data(), buf.size()});
}
template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(const S& fmt, const Args&... args) {
print(stdout, fmt, args...);
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
void print(const S& fmt, T&&... args) {
print(stdout, fmt, std::forward<T>(args)...);
}
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals {
template <detail::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>();
template <size_t N> class static_format_result {
private:
char data[N];
public:
template <typename S, typename... T,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
}
} // namespace literals
#endif
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; }
FMT_CONSTEXPR auto c_str() const -> const char* { return data; }
};
/**
* Formats arguments according to the format string `fmt_str` and produces
* a string of the exact required size at compile time. Both the format string
* and the arguments must be compile-time expressions.
*
* The resulting string can be accessed as a C string via `c_str()` or as
* a `fmt::string_view` via `str()`.
*
* **Example**:
*
* // Produces the static string "42" at compile time.
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
* const char* s = result.c_str();
*/
#define FMT_STATIC_FORMAT(fmt_str, ...) \
fmt::static_format_result< \
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
FMT_COMPILE(fmt_str), __VA_ARGS__)
FMT_END_EXPORT
FMT_END_NAMESPACE

View File

@ -22,7 +22,7 @@
#include "format.h"
#if FMT_USE_LOCALE
#if FMT_USE_LOCALE && !defined(FMT_MODULE)
# include <locale>
#endif
@ -30,15 +30,53 @@
# define FMT_FUNC
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if defined(FMT_USE_FULL_CACHE_DRAGONBOX)
// Use the provided definition.
#elif defined(__OPTIMIZE_SIZE__)
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
#else
# define FMT_USE_FULL_CACHE_DRAGONBOX 1
#endif
FMT_BEGIN_NAMESPACE
#ifndef FMT_CUSTOM_ASSERT_FAIL
FMT_FUNC void assert_fail(const char* file, int line, const char* message) {
// Use unchecked std::fprintf to avoid triggering another assertion when
// writing to stderr fails.
fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
std::fprintf(stderr, "%s:%d: assertion failed: %s", file, line, message);
abort();
}
#endif
#if FMT_USE_LOCALE
namespace detail {
using std::locale;
using std::numpunct;
using std::use_facet;
} // namespace detail
#else
namespace detail {
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
} // namespace detail
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
using namespace detail;
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
namespace detail {
FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
string_view message) noexcept {
@ -79,33 +117,6 @@ inline void fwrite_all(const void* ptr, size_t count, FILE* stream) {
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
}
#if FMT_USE_LOCALE
using std::locale;
using std::numpunct;
using std::use_facet;
template <typename Locale, enable_if_t<(sizeof(Locale::collate) != 0), int>>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, locale>::value, "");
}
#else
struct locale {};
template <typename Char> struct numpunct {
auto grouping() const -> std::string { return "\03"; }
auto thousands_sep() const -> Char { return ','; }
auto decimal_point() const -> Char { return '.'; }
};
template <typename Facet> Facet use_facet(locale) { return {}; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto locale_ref::get() const -> Locale {
static_assert(std::is_same<Locale, locale>::value, "");
#if FMT_USE_LOCALE
if (locale_) return *static_cast<const locale*>(locale_);
#endif
return locale();
}
template <typename Char>
FMT_FUNC auto thousands_sep_impl(locale_ref loc) -> thousands_sep_result<Char> {
auto&& facet = use_facet<numpunct<Char>>(loc.get<locale>());
@ -133,12 +144,13 @@ FMT_FUNC auto write_loc(appender out, loc_value value,
} // namespace detail
FMT_FUNC void report_error(const char* message) {
#if FMT_USE_EXCEPTIONS
throw format_error(message);
#else
fputs(message, stderr);
abort();
#if FMT_MSC_VERSION || defined(__NVCC__)
// Silence unreachable code warnings in MSVC and NVCC because these
// are nearly impossible to fix in a generic code.
volatile bool b = true;
if (!b) return;
#endif
FMT_THROW(format_error(message));
}
template <typename Locale> typename Locale::id format_facet<Locale>::id;
@ -172,11 +184,11 @@ inline auto operator==(basic_fp<F> x, basic_fp<F> y) -> bool {
}
// Compilers should be able to optimize this into the ror instruction.
FMT_CONSTEXPR inline auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
FMT_INLINE auto rotr(uint32_t n, uint32_t r) noexcept -> uint32_t {
r &= 31;
return (n >> r) | (n << (32 - r));
}
FMT_CONSTEXPR inline auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
FMT_INLINE auto rotr(uint64_t n, uint32_t r) noexcept -> uint64_t {
r &= 63;
return (n >> r) | (n << (64 - r));
}
@ -210,7 +222,7 @@ inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int {
return (e * 631305 - 261663) >> 21;
}
FMT_INLINE_VARIABLE constexpr struct {
FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct {
uint32_t divisor;
int shift_amount;
} div_small_pow10_infos[] = {{10, 16}, {100, 16}};
@ -273,7 +285,7 @@ template <> struct cache_accessor<float> {
static auto get_cached_power(int k) noexcept -> uint64_t {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range");
static constexpr const uint64_t pow10_significands[] = {
static constexpr uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@ -368,7 +380,7 @@ template <> struct cache_accessor<double> {
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
"k is out of range");
static constexpr const uint128_fallback pow10_significands[] = {
static constexpr uint128_fallback pow10_significands[] = {
#if FMT_USE_FULL_CACHE_DRAGONBOX
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0x9faacf3df73609b1, 0x77b191618c54e9ad},
@ -1035,7 +1047,7 @@ template <> struct cache_accessor<double> {
#if FMT_USE_FULL_CACHE_DRAGONBOX
return pow10_significands[k - float_info<double>::min_k];
#else
static constexpr const uint64_t powers_of_5_64[] = {
static constexpr uint64_t powers_of_5_64[] = {
0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,
@ -1095,7 +1107,7 @@ template <> struct cache_accessor<double> {
return {r.high(), r.low() == 0};
}
static auto compute_delta(cache_entry_type const& cache, int beta) noexcept
static auto compute_delta(const cache_entry_type& cache, int beta) noexcept
-> uint32_t {
return static_cast<uint32_t>(cache.high() >> (64 - 1 - beta));
}
@ -1147,8 +1159,8 @@ auto is_left_endpoint_integer_shorter_interval(int exponent) noexcept -> bool {
exponent <= case_shorter_interval_left_endpoint_upper_threshold;
}
// Remove trailing zeros from n and return the number of zeros removed (float)
FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
// Remove trailing zeros from n and return the number of zeros removed (float).
FMT_INLINE auto remove_trailing_zeros(uint32_t& n, int s = 0) noexcept -> int {
FMT_ASSERT(n != 0, "");
// Modular inverse of 5 (mod 2^32): (mod_inv_5 * 5) mod 2^32 = 1.
constexpr uint32_t mod_inv_5 = 0xcccccccd;
@ -1168,22 +1180,19 @@ FMT_INLINE int remove_trailing_zeros(uint32_t& n, int s = 0) noexcept {
return s;
}
// Removes trailing zeros and returns the number of zeros removed (double)
FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// Removes trailing zeros and returns the number of zeros removed (double).
FMT_INLINE auto remove_trailing_zeros(uint64_t& n) noexcept -> int {
FMT_ASSERT(n != 0, "");
// This magic number is ceil(2^90 / 10^8).
constexpr uint64_t magic_number = 12379400392853802749ull;
auto nm = umul128(n, magic_number);
// Is n is divisible by 10^8?
if ((nm.high() & ((1ull << (90 - 64)) - 1)) == 0 && nm.low() < magic_number) {
constexpr uint32_t ten_pow_8 = 100000000u;
if ((n % ten_pow_8) == 0) {
// If yes, work with the quotient...
auto n32 = static_cast<uint32_t>(nm.high() >> (90 - 64));
auto n32 = static_cast<uint32_t>(n / ten_pow_8);
// ... and use the 32 bit variant of the function
int s = remove_trailing_zeros(n32, 8);
int num_zeros = remove_trailing_zeros(n32, 8);
n = n32;
return s;
return num_zeros;
}
// If n is not divisible by 10^8, work with n itself.
@ -1208,7 +1217,7 @@ FMT_INLINE int remove_trailing_zeros(uint64_t& n) noexcept {
// The main algorithm for shorter interval case
template <typename T>
FMT_INLINE decimal_fp<T> shorter_interval_case(int exponent) noexcept {
FMT_INLINE auto shorter_interval_case(int exponent) noexcept -> decimal_fp<T> {
decimal_fp<T> ret_value;
// Compute k and beta
const int minus_k = floor_log10_pow2_minus_log10_4_over_3(exponent);
@ -1452,8 +1461,8 @@ FMT_FUNC void vformat_to(buffer<char>& buf, string_view fmt, format_args args,
auto out = appender(buf);
if (fmt.size() == 2 && equal2(fmt.data(), "{}"))
return args.get(0).visit(default_arg_formatter<char>{out});
parse_format_string(
fmt, format_handler<char>{parse_context<char>(fmt), {out, args, loc}});
parse_format_string(fmt,
format_handler<>{parse_context<>(fmt), {out, args, loc}});
}
template <typename T> struct span {
@ -1524,9 +1533,8 @@ template <typename F> class glibc_file : public file_base<F> {
}
void init_buffer() {
if (this->file_->_IO_write_ptr) return;
if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return;
// Force buffer initialization by placing and removing a char in a buffer.
assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end);
putc_unlocked(0, this->file_);
--this->file_->_IO_write_ptr;
}
@ -1545,10 +1553,11 @@ template <typename F> class glibc_file : public file_base<F> {
void advance_write_buffer(size_t size) { this->file_->_IO_write_ptr += size; }
bool needs_flush() const {
auto needs_flush() const -> bool {
if ((this->file_->_flags & line_buffered) == 0) return false;
char* end = this->file_->_IO_write_end;
return memchr(end, '\n', to_unsigned(this->file_->_IO_write_ptr - end));
auto size = max_of<ptrdiff_t>(this->file_->_IO_write_ptr - end, 0);
return memchr(end, '\n', static_cast<size_t>(size));
}
void flush() { fflush_unlocked(this->file_); }
@ -1572,7 +1581,7 @@ template <typename F> class apple_file : public file_base<F> {
void init_buffer() {
if (this->file_->_p) return;
// Force buffer initialization by placing and removing a char in a buffer.
putc_unlocked(0, this->file_);
if (!FMT_CLANG_ANALYZER) putc_unlocked(0, this->file_);
--this->file_->_p;
++this->file_->_w;
}
@ -1593,7 +1602,7 @@ template <typename F> class apple_file : public file_base<F> {
this->file_->_w -= size;
}
bool needs_flush() const {
auto needs_flush() const -> bool {
if ((this->file_->_flags & line_buffered) == 0) return false;
return memchr(this->file_->_p + this->file_->_w, '\n',
to_unsigned(-this->file_->_w));

File diff suppressed because it is too large Load Diff

View File

@ -29,7 +29,8 @@
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
defined(__linux__)) && \
(!defined(WINAPI_FAMILY) || \
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
!defined(__wasm__)
# include <fcntl.h> // for O_RDONLY
# define FMT_USE_FCNTL 1
# else
@ -135,10 +136,9 @@ FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
* **Example**:
*
* // This throws a system_error with the description
* // cannot open file 'madeup': The system cannot find the file
* specified.
* // or similar (system message may vary).
* const char *filename = "madeup";
* // cannot open file 'foo': The system cannot find the file specified.
* // or similar (system message may vary) if the file doesn't exist.
* const char *filename = "foo";
* LPOFSTRUCT of = LPOFSTRUCT();
* HFILE file = OpenFile(filename, &of, OF_READ);
* if (file == HFILE_ERROR) {
@ -161,14 +161,6 @@ inline auto system_category() noexcept -> const std::error_category& {
}
#endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
}
#endif
// A buffered file.
class buffered_file {
private:
@ -364,17 +356,17 @@ FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
/// A fast buffered output stream for writing from a single thread. Writing from
/// multiple threads without external synchronization may result in a data race.
class FMT_API ostream : private detail::buffer<char> {
class ostream : private detail::buffer<char> {
private:
file file_;
ostream(cstring_view path, const detail::ostream_params& params);
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
static void grow(buffer<char>& buf, size_t);
FMT_API static void grow(buffer<char>& buf, size_t);
public:
ostream(ostream&& other) noexcept;
~ostream();
FMT_API ostream(ostream&& other) noexcept;
FMT_API ~ostream();
operator writer() {
detail::buffer<char>& buf = *this;

View File

@ -33,8 +33,8 @@
FMT_BEGIN_NAMESPACE
namespace detail {
// Generate a unique explicit instantion in every translation unit using a tag
// type in an anonymous namespace.
// Generate a unique explicit instantiation in every translation unit using a
// tag type in an anonymous namespace.
namespace {
struct file_access_tag {};
} // namespace
@ -150,7 +150,7 @@ inline void vprint(std::ostream& os, string_view fmt, format_args args) {
FMT_EXPORT template <typename... T>
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::vargs<T...> vargs = {{args...}};
if (detail::use_utf8) return vprint(os, fmt.str, vargs);
if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt.str, vargs);
detail::write_buffer(os, buffer);
@ -158,7 +158,8 @@ void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
FMT_EXPORT template <typename... T>
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
fmt::print(os, "{}\n", fmt::format(fmt, std::forward<T>(args)...));
fmt::print(os, FMT_STRING("{}\n"),
fmt::format(fmt, std::forward<T>(args)...));
}
FMT_END_NAMESPACE

View File

@ -9,7 +9,7 @@
#define FMT_PRINTF_H_
#ifndef FMT_MODULE
# include <algorithm> // std::max
# include <algorithm> // std::find
# include <limits> // std::numeric_limits
#endif
@ -18,10 +18,6 @@
FMT_BEGIN_NAMESPACE
FMT_BEGIN_EXPORT
template <typename T> struct printf_formatter {
printf_formatter() = delete;
};
template <typename Char> class basic_printf_context {
private:
basic_appender<Char> out_;
@ -33,8 +29,6 @@ template <typename Char> class basic_printf_context {
public:
using char_type = Char;
using parse_context_type = parse_context<Char>;
template <typename T> using formatter_type = printf_formatter<T>;
enum { builtin_types = 1 };
/// Constructs a `printf_context` object. References to the arguments are
@ -46,7 +40,7 @@ template <typename Char> class basic_printf_context {
auto out() -> basic_appender<Char> { return out_; }
void advance_to(basic_appender<Char>) {}
auto locale() -> detail::locale_ref { return {}; }
auto locale() -> locale_ref { return {}; }
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
return args_.get(id);
@ -74,10 +68,9 @@ inline auto find<false, char>(const char* first, const char* last, char value,
// Checks if a value fits in int - used to avoid warnings about comparing
// signed and unsigned integers.
template <bool IsSigned> struct int_checker {
template <bool IS_SIGNED> struct int_checker {
template <typename T> static auto fits_in_int(T value) -> bool {
unsigned max = to_unsigned(max_value<int>());
return value <= max;
return value <= to_unsigned(max_value<int>());
}
inline static auto fits_in_int(bool) -> bool { return true; }
};
@ -95,7 +88,7 @@ struct printf_precision_handler {
auto operator()(T value) -> int {
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
report_error("number is too big");
return (std::max)(static_cast<int>(value), 0);
return max_of(static_cast<int>(value), 0);
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
@ -410,7 +403,9 @@ void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
arg_index = parse_ctx.next_arg_id();
else
parse_ctx.check_arg_id(--arg_index);
return detail::get_arg(context, arg_index);
auto arg = context.arg(arg_index);
if (!arg) report_error("argument not found");
return arg;
};
const Char* start = parse_ctx.begin();
@ -571,14 +566,18 @@ inline auto vsprintf(basic_string_view<Char> fmt,
*
* std::string message = fmt::sprintf("The answer is %d", 42);
*/
template <typename S, typename... T, typename Char = detail::char_t<S>>
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
return vsprintf(detail::to_string_view(fmt),
fmt::make_format_args<basic_printf_context<Char>>(args...));
template <typename... T>
inline auto sprintf(string_view fmt, const T&... args) -> std::string {
return vsprintf(fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
-> std::wstring {
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
}
template <typename Char>
inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args) -> int {
auto buf = basic_memory_buffer<Char>();
detail::vprintf(buf, fmt, args);
@ -596,17 +595,14 @@ inline auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
*
* fmt::fprintf(stderr, "Don't %s!", "panic");
*/
template <typename S, typename... T, typename Char = detail::char_t<S>>
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
return vfprintf(f, detail::to_string_view(fmt),
make_printf_args<Char>(args...));
template <typename... T>
inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
return vfprintf(f, fmt, make_printf_args(args...));
}
template <typename Char>
FMT_DEPRECATED inline auto vprintf(basic_string_view<Char> fmt,
typename vprintf_args<Char>::type args)
-> int {
return vfprintf(stdout, fmt, args);
template <typename... T>
FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
}
/**
@ -621,11 +617,6 @@ template <typename... T>
inline auto printf(string_view fmt, const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args(args...));
}
template <typename... T>
FMT_DEPRECATED inline auto printf(basic_string_view<wchar_t> fmt,
const T&... args) -> int {
return vfprintf(stdout, fmt, make_printf_args<wchar_t>(args...));
}
FMT_END_EXPORT
FMT_END_NAMESPACE

View File

@ -11,7 +11,6 @@
#ifndef FMT_MODULE
# include <initializer_list>
# include <iterator>
# include <string>
# include <tuple>
# include <type_traits>
# include <utility>
@ -19,6 +18,13 @@
#include "format.h"
#if FMT_HAS_CPP_ATTRIBUTE(clang::lifetimebound)
# define FMT_LIFETIMEBOUND [[clang::lifetimebound]]
#else
# define FMT_LIFETIMEBOUND
#endif
FMT_PRAGMA_CLANG(diagnostic error "-Wreturn-stack-address")
FMT_BEGIN_NAMESPACE
FMT_EXPORT
@ -31,7 +37,7 @@ template <typename T> class is_map {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -40,17 +46,16 @@ template <typename T> class is_set {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
};
// C array overload
template <typename T, std::size_t N>
template <typename T, size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
@ -120,7 +125,7 @@ template <typename T> class is_tuple_like_ {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -154,7 +159,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ {
public:
static constexpr const bool value = false;
static constexpr bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is>
@ -170,7 +175,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
C>::value)...>{}));
public:
static constexpr const bool value =
static constexpr bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
@ -208,7 +213,7 @@ template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
template <typename Tuple, typename Char, size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
@ -219,7 +224,7 @@ template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>()));
};
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
using type = T&;
};
@ -236,14 +241,6 @@ using range_reference_type =
template <typename Range>
using uncvref_type = remove_cvref_t<range_reference_type<Range>>;
template <typename Formatter>
FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set)
-> decltype(f.set_debug_format(set)) {
f.set_debug_format(set);
}
template <typename Formatter>
FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {}
template <typename T>
struct range_format_kind_
: std::integral_constant<range_format,
@ -281,14 +278,15 @@ template <typename FormatContext> struct format_tuple_element {
} // namespace detail
FMT_EXPORT
template <typename T> struct is_tuple_like {
static constexpr const bool value =
static constexpr bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
FMT_EXPORT
template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value =
detail::is_tuple_formattable_<T, C>::value;
static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
};
template <typename Tuple, typename Char>
@ -343,8 +341,9 @@ struct formatter<Tuple, Char,
}
};
FMT_EXPORT
template <typename T, typename Char> struct is_range {
static constexpr const bool value =
static constexpr bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
};
@ -368,6 +367,7 @@ template <typename P1, typename... Pn>
struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
FMT_EXPORT
template <typename T, typename Char, typename Enable = void>
struct range_formatter;
@ -527,7 +527,9 @@ struct formatter<
template <typename R, typename Char>
struct formatter<
R, Char,
enable_if_t<range_format_kind<R, Char>::value == range_format::map>> {
enable_if_t<conjunction<
bool_constant<range_format_kind<R, Char>::value == range_format::map>,
detail::is_formattable_delayed<R, Char>>::value>> {
private:
using map_type = detail::maybe_const_range<R>;
using element_type = detail::uncvref_type<map_type>;
@ -668,7 +670,8 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
}
};
template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
FMT_EXPORT
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
const Tuple& tuple;
basic_string_view<Char> sep;
@ -683,15 +686,15 @@ template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename Tuple>
struct formatter<tuple_join_view<Char, Tuple>, Char,
template <typename Tuple, typename Char>
struct formatter<tuple_join_view<Tuple, Char>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::tuple_size<Tuple>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, Tuple>& value,
auto format(const tuple_join_view<Tuple, Char>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, std::tuple_size<Tuple>());
}
@ -723,14 +726,14 @@ struct formatter<tuple_join_view<Char, Tuple>, Char,
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx,
auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,
auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
using std::get;
@ -752,7 +755,7 @@ template <typename T> class is_container_adaptor_like {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -772,13 +775,13 @@ struct formatter<
: formatter<detail::all<typename T::container_type>, Char> {
using all = detail::all<typename T::container_type>;
template <typename FormatContext>
auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) {
auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) {
struct getter : T {
static auto get(const T& t) -> all {
return {t.*(&getter::c)}; // Access c through the derived class.
static auto get(const T& v) -> all {
return {v.*(&getter::c)}; // Access c through the derived class.
}
};
return formatter<all>::format(getter::get(t), ctx);
return formatter<all>::format(getter::get(value), ctx);
}
};
@ -817,13 +820,13 @@ auto join(Range&& r, string_view sep)
*
* **Example**:
*
* auto t = std::tuple<int, char>{1, 'a'};
* auto t = std::tuple<int, char>(1, 'a');
* fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a
*/
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, Tuple> {
FMT_CONSTEXPR auto join(const Tuple& tuple FMT_LIFETIMEBOUND, string_view sep)
-> tuple_join_view<Tuple, char> {
return {tuple, sep};
}

View File

@ -15,15 +15,13 @@
# include <atomic>
# include <bitset>
# include <complex>
# include <cstdlib>
# include <exception>
# include <functional>
# include <functional> // std::reference_wrapper
# include <memory>
# include <thread>
# include <type_traits>
# include <typeinfo>
# include <utility>
# include <vector>
# include <typeinfo> // std::type_info
# include <utility> // std::make_index_sequence
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L
@ -62,36 +60,37 @@
# endif
#endif
// For older Xcode versions, __cpp_lib_xxx flags are inaccurately defined.
#ifndef FMT_CPP_LIB_FILESYSTEM
# ifdef __cpp_lib_filesystem
#ifdef FMT_CPP_LIB_FILESYSTEM
// Use the provided definition.
#elif defined(__cpp_lib_filesystem)
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
#else
# define FMT_CPP_LIB_FILESYSTEM 0
#endif
#endif
#ifndef FMT_CPP_LIB_VARIANT
# ifdef __cpp_lib_variant
#ifdef FMT_CPP_LIB_VARIANT
// Use the provided definition.
#elif defined(__cpp_lib_variant)
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
#else
# define FMT_CPP_LIB_VARIANT 0
#endif
#endif
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
else
if constexpr (std::is_same_v<Char, char> &&
std::is_same_v<PathChar, wchar_t>) {
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf);
} else {
return p.string<Char>();
}
}
template <typename Char, typename PathChar>
void write_escaped_path(basic_memory_buffer<Char>& quoted,
@ -111,9 +110,180 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
}
}
#endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T, typename FormatContext>
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
-> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
formatter<std::remove_cv_t<T>, Char> underlying;
maybe_set_debug_format(underlying, true);
return underlying.format(v, ctx);
}
#endif
#if FMT_CPP_LIB_VARIANT
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename Variant, typename Char> class is_variant_formattable {
template <size_t... Is>
static auto check(std::index_sequence<Is...>) -> std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
public:
static constexpr bool value = decltype(check(
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
};
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
char* begin) -> string_view {
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* to = begin + 5; // std::
for (const char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
const char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
return demangled_name_view;
}
template <class OutputIt>
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
-> OutputIt {
const string_view demangled_name(abi_name_view);
for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
}
template <typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = normalize_libcxx_inline_namespaces(
demangled_name_ptr.get(), demangled_name_ptr.get());
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<char>(out, demangled_name_view);
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
return normalize_msvc_abi_name(ti.name(), out);
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
const string_view demangled_name = ti.name();
std::string name_copy(demangled_name.size(), '\0');
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
// front of types
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
name_copy.end());
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
// environments, we need to eliminate both of them.
const string_view normalized_name =
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
return detail::write_bytes<char>(out, normalized_name);
# else
return detail::write_bytes<char>(out, string_view(ti.name()));
# endif
}
#endif // FMT_USE_RTTI
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr bool value = std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value &&
has_flip<T>::value;
};
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr bool value = true;
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>
struct has_format_as_member<
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
: std::true_type {};
} // namespace detail
FMT_EXPORT
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
#if FMT_CPP_LIB_FILESYSTEM
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs specs_;
@ -178,24 +348,20 @@ class path : public std::filesystem::path {
auto generic_system_string() const -> std::string { return generic_string(); }
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
template <size_t N, typename Char>
struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
private:
// Functor because C++11 doesn't support generic lambdas.
// This is a functor because C++11 doesn't support generic lambdas.
struct writer {
const std::bitset<N>& bs;
template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) {
for (auto pos = N; pos > 0; --pos)
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out;
}
};
@ -204,41 +370,28 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) {
return write_padded(ctx, writer{bs});
return this->write_padded(ctx, writer{bs});
}
};
FMT_EXPORT
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
private:
formatter<T, Char> underlying_;
formatter<std::remove_cv_t<T>, Char> underlying_;
static constexpr basic_string_view<Char> optional =
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
'('>{};
static constexpr basic_string_view<Char> none =
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
template <class U>
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
-> decltype(u.set_debug_format(set)) {
u.set_debug_format(set);
}
template <class U>
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
maybe_set_debug_format(underlying_, true);
detail::maybe_set_debug_format(underlying_, true);
return underlying_.parse(ctx);
}
@ -254,31 +407,9 @@ struct formatter<std::optional<T>, Char,
return detail::write(out, ')');
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
} // namespace detail
FMT_END_NAMESPACE
#endif
#ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char,
std::enable_if_t<(std::is_void<T>::value ||
@ -296,21 +427,18 @@ struct formatter<std::expected<T, E>, Char,
if (value.has_value()) {
out = detail::write<Char>(out, "expected(");
if constexpr (!std::is_void<T>::value)
out = detail::write_escaped_alternative<Char>(out, *value);
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
} else {
out = detail::write<Char>(out, "unexpected(");
out = detail::write_escaped_alternative<Char>(out, value.error());
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
}
*out++ = ')';
return out;
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::source_location> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
@ -328,45 +456,14 @@ template <> struct formatter<std::source_location> {
return out;
}
};
FMT_END_NAMESPACE
#endif
#if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
static constexpr bool value = detail::is_variant_like_<T>::value;
};
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
};
FMT_EXPORT
template <typename Char> struct formatter<std::monostate, Char> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
@ -379,12 +476,11 @@ template <typename Char> struct formatter<std::monostate, Char> {
}
};
FMT_EXPORT
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
struct formatter<Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
is_variant_like<Variant>,
detail::is_variant_formattable<Variant, Char>>>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
@ -398,7 +494,7 @@ struct formatter<
FMT_TRY {
std::visit(
[&](const auto& v) {
out = detail::write_escaped_alternative<Char>(out, v);
out = detail::write_escaped_alternative<Char>(out, v, ctx);
},
value);
}
@ -409,27 +505,36 @@ struct formatter<
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
FMT_EXPORT
template <> struct formatter<std::error_code> {
private:
format_specs specs_;
detail::arg_ref<char> width_ref_;
bool debug_ = false;
public:
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin(), end = ctx.end();
if (it == end) return it;
it = detail::parse_align(it, end, specs_);
if (it == end) return it;
char c = *it;
if ((c >= '0' && c <= '9') || c == '{')
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
if (it != end && *it == '?') {
debug_ = true;
++it;
}
if (it != end && *it == 's') {
specs_.set_type(presentation_type::string);
++it;
}
return it;
}
@ -439,113 +544,48 @@ template <> struct formatter<std::error_code> {
auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx);
memory_buffer buf;
auto buf = memory_buffer();
if (specs_.type() == presentation_type::string) {
buf.append(ec.message());
} else {
buf.append(string_view(ec.category().name()));
buf.push_back(':');
detail::write<char>(appender(buf), ec.value());
return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()),
specs);
}
auto quoted = memory_buffer();
auto str = string_view(buf.data(), buf.size());
if (debug_) {
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
str = string_view(quoted.data(), quoted.size());
}
return detail::write<char>(ctx.out(), str, specs);
}
};
#if FMT_USE_RTTI
namespace detail {
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
} // namespace detail
FMT_EXPORT
template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
template <> struct formatter<std::type_info> {
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
return ctx.begin();
}
template <typename Context>
auto format(const std::type_info& ti, Context& ctx) const
-> decltype(ctx.out()) {
return detail::write_demangled_name<Char>(ctx.out(), ti);
return detail::write_demangled_name(ctx.out(), ti);
}
};
#endif
#endif // FMT_USE_RTTI
FMT_EXPORT
template <typename T, typename Char>
template <typename T>
struct formatter<
T, Char, // DEPRECATED! Mixing code unit types.
T, char,
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
private:
bool with_typename_ = false;
public:
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
auto it = ctx.begin();
auto end = ctx.end();
if (it == end || *it == '}') return it;
@ -562,47 +602,18 @@ struct formatter<
auto out = ctx.out();
#if FMT_USE_RTTI
if (with_typename_) {
out = detail::write_demangled_name<Char>(out, typeid(ex));
out = detail::write_demangled_name(out, typeid(ex));
*out++ = ':';
*out++ = ' ';
}
#endif
return detail::write_bytes<Char>(out, string_view(ex.what()));
return detail::write_bytes<char>(out, string_view(ex.what()));
}
};
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization.
FMT_EXPORT
template <typename BitRef, typename Char>
struct formatter<BitRef, Char,
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
@ -614,15 +625,6 @@ struct formatter<BitRef, Char,
}
};
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
@ -635,7 +637,6 @@ struct formatter<std::atomic<T>, Char,
};
#ifdef __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename Char>
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
template <typename FormatContext>
@ -646,7 +647,11 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
};
#endif // __cpp_lib_atomic_flag_test
FMT_EXPORT
template <typename T> struct is_tuple_like;
template <typename T>
struct is_tuple_like<std::complex<T>> : std::false_type {};
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private:
detail::dynamic_format_specs<Char> specs_;
@ -695,9 +700,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs();
outer_specs.width = specs.width;
auto fill = specs.template fill<Char>();
if (fill)
outer_specs.set_fill(basic_string_view<Char>(fill, specs.fill_size()));
outer_specs.copy_fill_from(specs);
outer_specs.set_align(specs.align());
specs.width = 0;
@ -711,10 +714,13 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
}
};
FMT_EXPORT
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
// Guard against format_as because reference_wrapper is
// implicitly convertible to T&.
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
!detail::has_format_as<T>::value &&
!detail::has_format_as_member<T>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
@ -724,4 +730,5 @@ struct formatter<std::reference_wrapper<T>, Char,
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

View File

@ -55,6 +55,16 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
#endif
return false;
}
template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail
FMT_BEGIN_EXPORT
@ -112,14 +122,6 @@ inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}};
}
template <> struct is_char<wchar_t> : std::true_type {};
template <> struct is_char<char16_t> : std::true_type {};
template <> struct is_char<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
#endif
template <typename... T>
constexpr auto make_wformat_args(T&... args)
-> decltype(fmt::make_format_args<wformat_context>(args...)) {
@ -155,13 +157,13 @@ auto join(std::initializer_list<T> list, wstring_view sep)
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, Tuple> {
-> tuple_join_view<Tuple, wchar_t> {
return {tuple, sep};
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args)
basic_format_args<buffered_context<Char>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, fmt, args);
@ -193,19 +195,18 @@ auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
template <typename S, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto vformat(detail::locale_ref loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
inline auto vformat(locale_ref loc, const S& fmt,
basic_format_args<buffered_context<Char>> args)
-> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args,
detail::locale_ref(loc));
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
return {buf.data(), buf.size()};
}
template <typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
inline auto format(detail::locale_ref loc, const S& fmt, T&&... args)
inline auto format(locale_ref loc, const S& fmt, T&&... args)
-> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
@ -216,7 +217,7 @@ template <typename OutputIt, typename S,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& fmt,
typename detail::vformat_args<Char>::type args) -> OutputIt {
basic_format_args<buffered_context<Char>> args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out);
@ -236,11 +237,11 @@ template <typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
basic_format_args<buffered_context<Char>> args)
-> OutputIt {
auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc));
vformat_to(buf, detail::to_string_view(fmt), args, loc);
return detail::get_iterator(buf, out);
}
@ -248,9 +249,8 @@ template <typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt,
T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
-> typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...));
}
@ -259,7 +259,7 @@ template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
typename detail::vformat_args<Char>::type args)
basic_format_args<buffered_context<Char>> args)
-> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
@ -317,7 +317,7 @@ template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
}
inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
-> std::wstring {
auto buf = wmemory_buffer();
detail::vformat_to(buf, ts, fmt, args);
@ -325,23 +325,11 @@ inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args)
}
template <typename... T>
inline auto format(const text_style& ts, wformat_string<T...> fmt, T&&... args)
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
-> std::wstring {
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, const text_style& ts,
wformat_string<T...> fmt, const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(const text_style& ts, wformat_string<T...> fmt,
const T&... args) {
return print(stdout, ts, fmt, args...);
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args);

View File

@ -1,5 +1,7 @@
module;
#define FMT_MODULE
#ifdef _MSVC_LANG
# define FMT_CPLUSPLUS _MSVC_LANG
#else
@ -48,6 +50,8 @@ module;
# include <limits.h>
# include <stdint.h>
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h>
#endif
#include <cerrno>

View File

@ -8,6 +8,12 @@
#include "fmt/format-inl.h"
FMT_BEGIN_NAMESPACE
#if FMT_USE_LOCALE
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
namespace detail {
template FMT_API auto dragonbox::to_decimal(float x) noexcept
@ -15,12 +21,6 @@ template FMT_API auto dragonbox::to_decimal(float x) noexcept
template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>;
#if FMT_USE_LOCALE
// DEPRECATED! locale_ref in the detail namespace
template FMT_API locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif
// Explicit instantiations for char.
template FMT_API auto thousands_sep_impl(locale_ref)
@ -30,16 +30,13 @@ template FMT_API auto decimal_point_impl(locale_ref) -> char;
// DEPRECATED!
template FMT_API void buffer<char>::append(const char*, const char*);
// DEPRECATED!
template FMT_API void vformat_to(buffer<char>&, string_view,
typename vformat_args<>::type, locale_ref);
// Explicit instantiations for wchar_t.
template FMT_API auto thousands_sep_impl(locale_ref)
-> thousands_sep_result<wchar_t>;
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
// DEPRECATED!
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
} // namespace detail

View File

@ -66,14 +66,14 @@ using rwresult = int;
// On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) {
inline unsigned convert_rwcount(size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#elif FMT_USE_FCNTL
// Return type of read and write functions.
using rwresult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; }
inline auto convert_rwcount(size_t count) -> size_t { return count; }
#endif
} // namespace
@ -185,7 +185,7 @@ void buffered_file::close() {
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
int buffered_file::descriptor() const {
auto buffered_file::descriptor() const -> int {
#ifdef FMT_HAS_SYSTEM
// fileno is a macro on OpenBSD.
# ifdef fileno
@ -240,7 +240,7 @@ void file::close() {
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
}
long long file::size() const {
auto file::size() const -> long long {
# ifdef _WIN32
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
// is less than 0x0500 as is the case with some default MinGW builds.
@ -251,7 +251,7 @@ long long file::size() const {
if (size_lower == INVALID_FILE_SIZE) {
DWORD error = GetLastError();
if (error != NO_ERROR)
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
FMT_THROW(windows_error(error, "cannot get file size"));
}
unsigned long long long_size = size_upper;
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
@ -266,7 +266,7 @@ long long file::size() const {
# endif
}
std::size_t file::read(void* buffer, std::size_t count) {
auto file::read(void* buffer, size_t count) -> size_t {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0)
@ -274,7 +274,7 @@ std::size_t file::read(void* buffer, std::size_t count) {
return detail::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
auto file::write(const void* buffer, size_t count) -> size_t {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0)
@ -282,7 +282,7 @@ std::size_t file::write(const void* buffer, std::size_t count) {
return detail::to_unsigned(result);
}
file file::dup(int fd) {
auto file::dup(int fd) -> file {
// Don't retry as dup doesn't return EINTR.
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
int new_fd = FMT_POSIX_CALL(dup(fd));
@ -308,7 +308,7 @@ void file::dup2(int fd, std::error_code& ec) noexcept {
if (result == -1) ec = std::error_code(errno, std::generic_category());
}
buffered_file file::fdopen(const char* mode) {
auto file::fdopen(const char* mode) -> buffered_file {
// Don't retry as fdopen doesn't return EINTR.
# if defined(__MINGW32__) && defined(_POSIX_)
FILE* f = ::fdopen(fd_, mode);
@ -355,7 +355,7 @@ pipe::pipe() {
}
# if !defined(__MSDOS__)
long getpagesize() {
auto getpagesize() -> long {
# ifdef _WIN32
SYSTEM_INFO si;
GetSystemInfo(&si);

View File

@ -1 +0,0 @@
7.1.2

View File

@ -1,20 +0,0 @@
cc_library(
name = "fmt",
srcs = [
#"src/fmt.cc", # No C++ module support, yet in Bazel (https://github.com/bazelbuild/bazel/pull/19940)
"src/format.cc",
"src/os.cc",
],
hdrs = glob([
"include/fmt/*.h",
]),
copts = select({
"@platforms//os:windows": ["-utf-8"],
"//conditions:default": [],
}),
includes = [
"include",
],
strip_include_prefix = "include",
visibility = ["//visibility:public"],
)

View File

@ -1,6 +0,0 @@
module(
name = "fmt",
compatibility_level = 10,
)
bazel_dep(name = "platforms", version = "0.0.10")

View File

@ -1,28 +0,0 @@
# Bazel support
To get [Bazel](https://bazel.build/) working with {fmt} you can copy the files `BUILD.bazel`,
`MODULE.bazel`, `WORKSPACE.bazel`, and `.bazelversion` from this folder (`support/bazel`) to the root folder of this project.
This way {fmt} gets bazelized and can be used with Bazel (e.g. doing a `bazel build //...` on {fmt}).
## Using {fmt} as a dependency
### Using Bzlmod
The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registry/tree/main/modules/fmt) provides support for {fmt}.
For instance, to use {fmt} add to your `MODULE.bazel` file:
```
bazel_dep(name = "fmt", version = "10.2.1")
```
### Live at head
For a live-at-head approach, you can copy the contents of this repository and move the Bazel-related build files to the root folder of this project as described above and make use of `local_path_override`, e.g.:
```
local_path_override(
module_name = "fmt",
path = "../third_party/fmt",
)
```

View File

@ -1,2 +0,0 @@
# WORKSPACE marker file needed by Bazel

View File

@ -2,6 +2,10 @@
# A script to invoke mkdocs with the correct environment.
# Additionally supports deploying via mike:
# ./mkdocs deploy [mike-deploy-options]
# For example:
# ./mkdocs deploy <version>
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
# <version> there.
import errno, os, shutil, sys
from subprocess import call
@ -40,7 +44,7 @@ config_path = os.path.join(support_dir, 'mkdocs.yml')
args = sys.argv[1:]
if len(args) > 0:
command = args[0]
if command == 'deploy':
if command == 'deploy' or command == 'set-default':
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
site_repo = git_url + 'fmtlib/fmt.dev.git'
@ -63,7 +67,13 @@ if len(args) > 0:
'--branch', 'master'], cwd=site_dir, env=env)
if ret != 0 or version == 'dev':
sys.exit(ret)
redirect_page_path = os.path.join(site_dir, version, 'api.html')
current_doc_path = os.path.join(site_dir, version)
# mike stages files added by deploy for deletion for unclear reason,
# undo it.
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
if False:
os.makedirs(current_doc_path, exist_ok=True)
redirect_page_path = os.path.join(current_doc_path, 'api.html')
with open(redirect_page_path, "w") as file:
file.write(redirect_page)
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)

View File

@ -34,8 +34,8 @@ tag_map = {
'emphasis': 'em',
'computeroutput': 'code',
'para': 'p',
'programlisting': 'pre',
'verbatim': 'pre'
'itemizedlist': 'ul',
'listitem': 'li'
}
# A map from Doxygen tags to text.
@ -50,21 +50,37 @@ def escape_html(s: str) -> str:
return s.replace("<", "&lt;")
# Converts a node from doxygen to HTML format.
def convert_node(node: ElementTree.Element, tag: str, attrs: dict = {}):
out = '<' + tag
for key, value in attrs.items():
out += ' ' + key + '="' + value + '"'
out += '>'
if node.text:
out += escape_html(node.text)
out += doxyxml2html(list(node))
out += '</' + tag + '>'
if node.tail:
out += node.tail
return out
def doxyxml2html(nodes: List[ElementTree.Element]):
out = ''
for n in nodes:
tag = tag_map.get(n.tag)
if not tag:
if tag:
out += convert_node(n, tag)
continue
if n.tag == 'programlisting' or n.tag == 'verbatim':
out += '<pre>'
out += convert_node(n, 'code', {'class': 'language-cpp'})
out += '</pre>'
continue
if n.tag == 'ulink':
out += convert_node(n, 'a', {'href': n.attrib['url']})
continue
out += tag_text_map[n.tag]
out += '<' + tag + '>' if tag else ''
out += '<code class="language-cpp">' if tag == 'pre' else ''
if n.text:
out += escape_html(n.text)
out += doxyxml2html(list(n))
out += '</code>' if tag == 'pre' else ''
out += '</' + tag + '>' if tag else ''
if n.tail:
out += n.tail
return out

View File

@ -62,11 +62,6 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS))
endif ()
add_fmt_test(ostream-test)
add_fmt_test(compile-test)
add_fmt_test(compile-fp-test)
if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif()
add_fmt_test(printf-test)
add_fmt_test(ranges-test ranges-odr-test.cc)
add_fmt_test(no-builtin-types-test HEADER_ONLY)

View File

@ -64,7 +64,7 @@ TEST(args_test, custom_format) {
}
struct to_stringable {
friend fmt::string_view to_string_view(to_stringable) { return {}; }
friend auto to_string_view(to_stringable) -> fmt::string_view { return {}; }
};
FMT_BEGIN_NAMESPACE

View File

@ -5,14 +5,16 @@
//
// For the license information refer to format.h.
// Turn assertion failures into exceptions for testing.
// clang-format off
#include "test-assert.h"
// clang-format on
#include "fmt/base.h"
#include <climits> // INT_MAX
#include <cstring> // std::strlen
#include <limits.h> // INT_MAX
#include <string.h> // strlen
#include <functional> // std::equal_to
#include <iterator> // std::back_insert_iterator, std::distance
#include <limits> // std::numeric_limits
@ -21,39 +23,36 @@
#include "gmock/gmock.h"
using fmt::string_view;
using fmt::detail::buffer;
#ifdef FMT_FORMAT_H_
# error base-test includes format.h
#endif
using testing::_;
using testing::Invoke;
using testing::Return;
#ifdef FMT_FORMAT_H_
# error core-test includes format.h
#endif
fmt::appender copy(fmt::string_view s, fmt::appender out) {
auto copy(fmt::string_view s, fmt::appender out) -> fmt::appender {
for (char c : s) *out++ = c;
return out;
}
TEST(string_view_test, value_type) {
static_assert(std::is_same<string_view::value_type, char>::value, "");
static_assert(std::is_same<fmt::string_view::value_type, char>::value, "");
}
TEST(string_view_test, ctor) {
EXPECT_STREQ("abc", fmt::string_view("abc").data());
EXPECT_EQ(3u, fmt::string_view("abc").size());
EXPECT_STREQ(fmt::string_view("abc").data(), "abc");
EXPECT_EQ(fmt::string_view("abc").size(), 3u);
EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data());
EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size());
EXPECT_STREQ(fmt::string_view(std::string("defg")).data(), "defg");
EXPECT_EQ(fmt::string_view(std::string("defg")).size(), 4u);
}
TEST(string_view_test, length) {
// Test that string_view::size() returns string length, not buffer size.
char str[100] = "some string";
EXPECT_EQ(std::strlen(str), string_view(str).size());
EXPECT_LT(std::strlen(str), sizeof(str));
EXPECT_EQ(fmt::string_view(str).size(), strlen(str));
EXPECT_LT(strlen(str), sizeof(str));
}
// Check string_view's comparison operator.
@ -62,13 +61,16 @@ template <template <typename> class Op> void check_op() {
size_t num_inputs = sizeof(inputs) / sizeof(*inputs);
for (size_t i = 0; i < num_inputs; ++i) {
for (size_t j = 0; j < num_inputs; ++j) {
string_view lhs(inputs[i]), rhs(inputs[j]);
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0), Op<string_view>()(lhs, rhs));
fmt::string_view lhs(inputs[i]), rhs(inputs[j]);
EXPECT_EQ(Op<int>()(lhs.compare(rhs), 0),
Op<fmt::string_view>()(lhs, rhs));
}
}
}
TEST(string_view_test, compare) {
using fmt::string_view;
EXPECT_EQ(string_view("foo").compare(string_view("foo")), 0);
EXPECT_GT(string_view("fop").compare(string_view("foo")), 0);
EXPECT_LT(string_view("foo").compare(string_view("fop")), 0);
@ -92,6 +94,167 @@ TEST(string_view_test, compare) {
check_op<std::greater_equal>();
}
#if FMT_USE_CONSTEVAL
TEST(string_view_test, from_constexpr_fixed_string) {
constexpr int size = 4;
struct fixed_string {
char data[size] = {};
constexpr fixed_string(const char (&m)[size]) {
for (size_t i = 0; i != size; ++i) data[i] = m[i];
}
};
static constexpr auto fs = fixed_string("foo");
static constexpr auto sv = fmt::string_view(fs.data);
EXPECT_EQ(sv, "foo");
}
#endif // FMT_USE_CONSTEVAL
TEST(buffer_test, noncopyable) {
EXPECT_FALSE(std::is_copy_constructible<fmt::detail::buffer<char>>::value);
EXPECT_FALSE(std::is_copy_assignable<fmt::detail::buffer<char>>::value);
}
TEST(buffer_test, nonmoveable) {
EXPECT_FALSE(std::is_move_constructible<fmt::detail::buffer<char>>::value);
EXPECT_FALSE(std::is_move_assignable<fmt::detail::buffer<char>>::value);
}
TEST(buffer_test, indestructible) {
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
"buffer's destructor is protected");
}
template <typename T> struct mock_buffer final : fmt::detail::buffer<T> {
MOCK_METHOD(size_t, do_grow, (size_t));
static void grow(fmt::detail::buffer<T>& buf, size_t capacity) {
auto& self = static_cast<mock_buffer&>(buf);
self.set(buf.data(), self.do_grow(capacity));
}
mock_buffer(T* data = nullptr, size_t buf_capacity = 0)
: fmt::detail::buffer<T>(grow) {
this->set(data, buf_capacity);
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
return capacity;
}));
}
};
TEST(buffer_test, ctor) {
{
mock_buffer<int> buffer;
EXPECT_EQ(buffer.data(), nullptr);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), 0u);
}
{
int data;
mock_buffer<int> buffer(&data);
EXPECT_EQ(&buffer[0], &data);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), 0u);
}
{
int data;
size_t capacity = std::numeric_limits<size_t>::max();
mock_buffer<int> buffer(&data, capacity);
EXPECT_EQ(&buffer[0], &data);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), capacity);
}
}
TEST(buffer_test, access) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
buffer[0] = 11;
EXPECT_EQ(buffer[0], 11);
buffer[3] = 42;
EXPECT_EQ(*(&buffer[0] + 3), 42);
const fmt::detail::buffer<char>& const_buffer = buffer;
EXPECT_EQ(const_buffer[3], 42);
}
TEST(buffer_test, try_resize) {
char data[123];
mock_buffer<char> buffer(data, sizeof(data));
buffer[10] = 42;
EXPECT_EQ(buffer[10], 42);
buffer.try_resize(20);
EXPECT_EQ(buffer.size(), 20u);
EXPECT_EQ(buffer.capacity(), 123u);
EXPECT_EQ(buffer[10], 42);
buffer.try_resize(5);
EXPECT_EQ(buffer.size(), 5u);
EXPECT_EQ(buffer.capacity(), 123u);
EXPECT_EQ(buffer[10], 42);
// Check if try_resize calls grow.
EXPECT_CALL(buffer, do_grow(124));
buffer.try_resize(124);
EXPECT_CALL(buffer, do_grow(200));
buffer.try_resize(200);
}
TEST(buffer_test, try_resize_partial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
buffer.try_resize(20);
EXPECT_EQ(buffer.capacity(), 15);
EXPECT_EQ(buffer.size(), 15);
}
TEST(buffer_test, clear) {
mock_buffer<char> buffer;
EXPECT_CALL(buffer, do_grow(20));
buffer.try_resize(20);
buffer.try_resize(0);
EXPECT_EQ(buffer.size(), 0u);
EXPECT_EQ(buffer.capacity(), 20u);
}
TEST(buffer_test, append) {
char data[15];
mock_buffer<char> buffer(data, 10);
auto test = "test";
buffer.append(test, test + 5);
EXPECT_STREQ(&buffer[0], test);
EXPECT_EQ(buffer.size(), 5u);
buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(12));
buffer.append(test, test + 2);
EXPECT_EQ(buffer[10], 't');
EXPECT_EQ(buffer[11], 'e');
EXPECT_EQ(buffer.size(), 12u);
}
TEST(buffer_test, append_partial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
testing::InSequence seq;
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10));
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) {
EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789");
buffer.clear();
return 10;
}));
auto test = "0123456789abcde";
buffer.append(test, test + 15);
}
TEST(buffer_test, append_allocates_enough_storage) {
char data[19];
mock_buffer<char> buffer(data, 10);
auto test = "abcdefgh";
buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(19));
buffer.append(test, test + 9);
}
TEST(base_test, is_locking) {
EXPECT_FALSE(fmt::detail::is_locking<const char(&)[3]>());
}
@ -116,154 +279,15 @@ TEST(base_test, is_back_insert_iterator) {
std::front_insert_iterator<std::string>>::value);
}
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 470
TEST(buffer_test, noncopyable) {
EXPECT_FALSE(std::is_copy_constructible<buffer<char>>::value);
# if !FMT_MSC_VERSION
// std::is_copy_assignable is broken in MSVC2013.
EXPECT_FALSE(std::is_copy_assignable<buffer<char>>::value);
# endif
}
TEST(buffer_test, nonmoveable) {
EXPECT_FALSE(std::is_move_constructible<buffer<char>>::value);
# if !FMT_MSC_VERSION
// std::is_move_assignable is broken in MSVC2013.
EXPECT_FALSE(std::is_move_assignable<buffer<char>>::value);
# endif
}
#endif
TEST(buffer_test, indestructible) {
static_assert(!std::is_destructible<fmt::detail::buffer<int>>(),
"buffer's destructor is protected");
}
template <typename T> struct mock_buffer final : buffer<T> {
MOCK_METHOD(size_t, do_grow, (size_t));
static void grow(buffer<T>& buf, size_t capacity) {
auto& self = static_cast<mock_buffer&>(buf);
self.set(buf.data(), self.do_grow(capacity));
}
mock_buffer(T* data = nullptr, size_t buf_capacity = 0) : buffer<T>(grow) {
this->set(data, buf_capacity);
ON_CALL(*this, do_grow(_)).WillByDefault(Invoke([](size_t capacity) {
return capacity;
}));
}
struct minimal_container {
using value_type = char;
void push_back(char) {}
};
TEST(buffer_test, ctor) {
{
mock_buffer<int> buffer;
EXPECT_EQ(nullptr, buffer.data());
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
}
{
int dummy;
mock_buffer<int> buffer(&dummy);
EXPECT_EQ(&dummy, &buffer[0]);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(static_cast<size_t>(0), buffer.capacity());
}
{
int dummy;
size_t capacity = std::numeric_limits<size_t>::max();
mock_buffer<int> buffer(&dummy, capacity);
EXPECT_EQ(&dummy, &buffer[0]);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(capacity, buffer.capacity());
}
}
TEST(buffer_test, access) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
buffer[0] = 11;
EXPECT_EQ(11, buffer[0]);
buffer[3] = 42;
EXPECT_EQ(42, *(&buffer[0] + 3));
const fmt::detail::buffer<char>& const_buffer = buffer;
EXPECT_EQ(42, const_buffer[3]);
}
TEST(buffer_test, try_resize) {
char data[123];
mock_buffer<char> buffer(data, sizeof(data));
buffer[10] = 42;
EXPECT_EQ(42, buffer[10]);
buffer.try_resize(20);
EXPECT_EQ(20u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
buffer.try_resize(5);
EXPECT_EQ(5u, buffer.size());
EXPECT_EQ(123u, buffer.capacity());
EXPECT_EQ(42, buffer[10]);
// Check if try_resize calls grow.
EXPECT_CALL(buffer, do_grow(124));
buffer.try_resize(124);
EXPECT_CALL(buffer, do_grow(200));
buffer.try_resize(200);
}
TEST(buffer_test, try_resize_partial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
EXPECT_CALL(buffer, do_grow(20)).WillOnce(Return(15));
buffer.try_resize(20);
EXPECT_EQ(buffer.capacity(), 15);
EXPECT_EQ(buffer.size(), 15);
}
TEST(buffer_test, clear) {
mock_buffer<char> buffer;
EXPECT_CALL(buffer, do_grow(20));
buffer.try_resize(20);
buffer.try_resize(0);
EXPECT_EQ(static_cast<size_t>(0), buffer.size());
EXPECT_EQ(20u, buffer.capacity());
}
TEST(buffer_test, append) {
char data[15];
mock_buffer<char> buffer(data, 10);
auto test = "test";
buffer.append(test, test + 5);
EXPECT_STREQ(test, &buffer[0]);
EXPECT_EQ(5u, buffer.size());
buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(12));
buffer.append(test, test + 2);
EXPECT_EQ('t', buffer[10]);
EXPECT_EQ('e', buffer[11]);
EXPECT_EQ(12u, buffer.size());
}
TEST(buffer_test, append_partial) {
char data[10];
mock_buffer<char> buffer(data, sizeof(data));
testing::InSequence seq;
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Return(10));
EXPECT_CALL(buffer, do_grow(15)).WillOnce(Invoke([&buffer](size_t) {
EXPECT_EQ(fmt::string_view(buffer.data(), buffer.size()), "0123456789");
buffer.clear();
return 10;
}));
auto test = "0123456789abcde";
buffer.append(test, test + 15);
}
TEST(buffer_test, append_allocates_enough_storage) {
char data[19];
mock_buffer<char> buffer(data, 10);
auto test = "abcdefgh";
buffer.try_resize(10);
EXPECT_CALL(buffer, do_grow(19));
buffer.append(test, test + 9);
TEST(base_test, copy) {
minimal_container c;
static constexpr char str[] = "a";
fmt::detail::copy<char>(str, str + 1, std::back_inserter(c));
}
TEST(base_test, get_buffer) {
@ -290,11 +314,6 @@ template <typename Char> struct formatter<test_struct, Char> {
};
FMT_END_NAMESPACE
TEST(arg_test, format_args) {
auto args = fmt::format_args();
EXPECT_FALSE(args.get(1));
}
// Use a unique result type to make sure that there are no undesirable
// conversions.
struct test_result {};
@ -360,33 +379,9 @@ VISIT_TYPE(unsigned long, unsigned long long);
CHECK_ARG(expected, value) \
}
template <typename T> class numeric_arg_test : public testing::Test {};
#if FMT_BUILTIN_TYPES
using test_types =
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
unsigned, long, unsigned long, long long, unsigned long long,
float, double, long double>;
#else
using test_types = testing::Types<int>;
#endif
TYPED_TEST_SUITE(numeric_arg_test, test_types);
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
auto test_value() -> T {
return static_cast<T>(42);
}
template <typename T,
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
auto test_value() -> T {
return static_cast<T>(4.2);
}
TYPED_TEST(numeric_arg_test, make_and_visit) {
CHECK_ARG_SIMPLE(test_value<TypeParam>());
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::min());
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
TEST(arg_test, format_args) {
auto args = fmt::format_args();
EXPECT_FALSE(args.get(1));
}
TEST(arg_test, char_arg) { CHECK_ARG('a', 'a'); }
@ -428,7 +423,7 @@ struct check_custom {
auto parse_ctx = fmt::format_parse_context("");
auto ctx = fmt::format_context(fmt::appender(buffer), fmt::format_args());
h.format(parse_ctx, ctx);
EXPECT_EQ("test", std::string(buffer.data, buffer.size()));
EXPECT_EQ(std::string(buffer.data, buffer.size()), "test");
return test_result();
}
};
@ -448,27 +443,57 @@ TEST(arg_test, visit_invalid_arg) {
fmt::basic_format_arg<fmt::format_context>().visit(visitor);
}
template <typename T> class numeric_arg_test : public testing::Test {};
#if FMT_BUILTIN_TYPES
using test_types =
testing::Types<bool, signed char, unsigned char, short, unsigned short, int,
unsigned, long, unsigned long, long long, unsigned long long,
float, double, long double>;
#else
using test_types = testing::Types<int>;
#endif
TYPED_TEST_SUITE(numeric_arg_test, test_types);
template <typename T, fmt::enable_if_t<std::is_integral<T>::value, int> = 0>
auto test_value() -> T {
return static_cast<T>(42);
}
template <typename T,
fmt::enable_if_t<std::is_floating_point<T>::value, int> = 0>
auto test_value() -> T {
return static_cast<T>(4.2);
}
TYPED_TEST(numeric_arg_test, make_and_visit) {
CHECK_ARG_SIMPLE(test_value<TypeParam>());
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::min());
CHECK_ARG_SIMPLE(std::numeric_limits<TypeParam>::max());
}
#if FMT_USE_CONSTEXPR
enum class arg_id_result { none, index, name };
struct test_arg_id_handler {
arg_id_result res = arg_id_result::none;
int index = 0;
string_view name;
fmt::string_view name;
constexpr void on_index(int i) {
res = arg_id_result::index;
index = i;
}
constexpr void on_name(string_view n) {
constexpr void on_name(fmt::string_view n) {
res = arg_id_result::name;
name = n;
}
};
template <size_t N>
constexpr test_arg_id_handler parse_arg_id(const char (&s)[N]) {
constexpr auto parse_arg_id(const char (&s)[N]) -> test_arg_id_handler {
auto h = test_arg_id_handler();
fmt::detail::parse_arg_id(s, s + N, h);
return h;
@ -526,7 +551,7 @@ struct test_format_string_handler {
bool error = false;
};
template <size_t N> constexpr bool parse_string(const char (&s)[N]) {
template <size_t N> constexpr auto parse_string(const char (&s)[N]) -> bool {
auto h = test_format_string_handler();
fmt::detail::parse_format_string(fmt::string_view(s, N - 1), h);
return !h.error;
@ -540,6 +565,7 @@ TEST(base_test, constexpr_parse_format_string) {
static_assert(parse_string("{foo}"), "");
static_assert(parse_string("{:}"), "");
}
#endif // FMT_USE_CONSTEXPR
struct enabled_formatter {};
@ -684,46 +710,46 @@ TEST(base_test, format_to) {
TEST(base_test, format_to_array) {
char buffer[4];
auto result = fmt::format_to(buffer, "{}", 12345);
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated);
EXPECT_EQ(buffer + 4, result.out);
EXPECT_EQ("1234", fmt::string_view(buffer, 4));
EXPECT_EQ(result.out, buffer + 4);
EXPECT_EQ(fmt::string_view(buffer, 4), "1234");
char* out = nullptr;
EXPECT_THROW(out = result, std::runtime_error);
(void)out;
result = fmt::format_to(buffer, "{:s}", "foobar");
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated);
EXPECT_EQ(buffer + 4, result.out);
EXPECT_EQ("foob", fmt::string_view(buffer, 4));
EXPECT_EQ(result.out, buffer + 4);
EXPECT_EQ(fmt::string_view(buffer, 4), "foob");
buffer[0] = 'x';
buffer[1] = 'x';
buffer[2] = 'x';
buffer[3] = 'x';
result = fmt::format_to(buffer, "{}", 'A');
EXPECT_EQ(1, std::distance(&buffer[0], result.out));
EXPECT_EQ(std::distance(&buffer[0], result.out), 1);
EXPECT_FALSE(result.truncated);
EXPECT_EQ(buffer + 1, result.out);
EXPECT_EQ("Axxx", fmt::string_view(buffer, 4));
EXPECT_EQ(result.out, buffer + 1);
EXPECT_EQ(fmt::string_view(buffer, 4), "Axxx");
result = fmt::format_to(buffer, "{}{} ", 'B', 'C');
EXPECT_EQ(3, std::distance(&buffer[0], result.out));
EXPECT_EQ(std::distance(&buffer[0], result.out), 3);
EXPECT_FALSE(result.truncated);
EXPECT_EQ(buffer + 3, result.out);
EXPECT_EQ("BC x", fmt::string_view(buffer, 4));
EXPECT_EQ(result.out, buffer + 3);
EXPECT_EQ(fmt::string_view(buffer, 4), "BC x");
result = fmt::format_to(buffer, "{}", "ABCDE");
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated);
EXPECT_EQ("ABCD", fmt::string_view(buffer, 4));
EXPECT_EQ(fmt::string_view(buffer, 4), "ABCD");
result = fmt::format_to(buffer, "{}", std::string(1000, '*').c_str());
EXPECT_EQ(4, std::distance(&buffer[0], result.out));
EXPECT_EQ(std::distance(&buffer[0], result.out), 4);
EXPECT_TRUE(result.truncated);
EXPECT_EQ("****", fmt::string_view(buffer, 4));
EXPECT_EQ(fmt::string_view(buffer, 4), "****");
}
// Test that check is not found by ADL.
@ -790,7 +816,7 @@ TEST(base_test, format_nonconst) {
}
TEST(base_test, throw_in_buffer_dtor) {
enum { buffer_size = 256 };
constexpr int buffer_size = 256;
struct throwing_iterator {
int& count;
@ -812,7 +838,7 @@ TEST(base_test, throw_in_buffer_dtor) {
}
}
struct its_a_trap {
struct convertible_to_any_type_with_member_x {
template <typename T> operator T() const {
auto v = T();
v.x = 42;
@ -821,12 +847,12 @@ struct its_a_trap {
};
FMT_BEGIN_NAMESPACE
template <> struct formatter<its_a_trap> {
template <> struct formatter<convertible_to_any_type_with_member_x> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(its_a_trap, format_context& ctx) const
auto format(convertible_to_any_type_with_member_x, format_context& ctx) const
-> decltype(ctx.out()) const {
auto out = ctx.out();
*out++ = 'x';
@ -835,9 +861,10 @@ template <> struct formatter<its_a_trap> {
};
FMT_END_NAMESPACE
TEST(base_test, trappy_conversion) {
TEST(base_test, promiscuous_conversions) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", its_a_trap());
fmt::format_to(std::back_inserter(s), "{}",
convertible_to_any_type_with_member_x());
EXPECT_EQ(s, "x");
}
@ -846,11 +873,11 @@ struct custom_container {
using value_type = char;
size_t size() const { return 0; }
auto size() const -> size_t { return 0; }
void resize(size_t) {}
void push_back(char) {}
char& operator[](size_t) { return data; }
auto operator[](size_t) -> char& { return data; }
};
FMT_BEGIN_NAMESPACE
@ -862,16 +889,23 @@ TEST(base_test, format_to_custom_container) {
fmt::format_to(std::back_inserter(c), "");
}
TEST(base_test, no_repeated_format_string_conversions) {
struct nondeterministic_format_string {
mutable int i = 0;
FMT_CONSTEXPR operator string_view() const {
return string_view("{}", i++ != 0 ? 2 : 0);
FMT_CONSTEXPR operator fmt::string_view() const {
return {"{}", i++ != 0 ? 2u : 0u};
}
};
TEST(base_test, no_repeated_format_string_conversions) {
#if !FMT_GCC_VERSION
#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 1200
char buf[10];
fmt::format_to(buf, nondeterministic_format_string());
#endif
}
TEST(base_test, format_context_accessors) {
auto copy = [](fmt::appender app, const fmt::format_context& ctx) {
return fmt::format_context(app, ctx.args(), ctx.locale());
};
fmt::detail::ignore_unused(copy);
}

View File

@ -15,11 +15,9 @@
#include "util.h" // get_locale
using fmt::runtime;
using fmt::sys_time;
using testing::Contains;
template <typename Duration>
using sys_time = std::chrono::time_point<std::chrono::system_clock, Duration>;
#if defined(__MINGW32__) && !defined(_UCRT)
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
# define FMT_HAS_C99_STRFTIME 0
@ -57,8 +55,8 @@ auto make_second(int s) -> std::tm {
return time;
}
std::string system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) {
auto system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) -> std::string {
auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<char>>(loc);
std::ostringstream os;
@ -75,8 +73,8 @@ std::string system_strftime(const std::string& format, const std::tm* timeptr,
#endif
}
FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
int sec) {
FMT_CONSTEXPR auto make_tm(int year, int mon, int mday, int hour, int min,
int sec) -> std::tm {
auto tm = std::tm();
tm.tm_sec = sec;
tm.tm_min = min;
@ -240,196 +238,145 @@ TEST(chrono_test, format_to_empty_container) {
EXPECT_EQ(s, "42");
}
TEST(chrono_test, empty_result) { EXPECT_EQ(fmt::format("{}", std::tm()), ""); }
auto equal(const std::tm& lhs, const std::tm& rhs) -> bool {
return lhs.tm_sec == rhs.tm_sec && lhs.tm_min == rhs.tm_min &&
lhs.tm_hour == rhs.tm_hour && lhs.tm_mday == rhs.tm_mday &&
lhs.tm_mon == rhs.tm_mon && lhs.tm_year == rhs.tm_year &&
lhs.tm_wday == rhs.tm_wday && lhs.tm_yday == rhs.tm_yday &&
lhs.tm_isdst == rhs.tm_isdst;
}
TEST(chrono_test, gmtime) {
auto t = std::time(nullptr);
auto tm = *std::gmtime(&t);
EXPECT_TRUE(equal(tm, fmt::gmtime(t)));
auto expected = *std::gmtime(&t);
auto actual = fmt::gmtime(t);
EXPECT_EQ(actual.tm_sec, expected.tm_sec);
EXPECT_EQ(actual.tm_min, expected.tm_min);
EXPECT_EQ(actual.tm_hour, expected.tm_hour);
EXPECT_EQ(actual.tm_mday, expected.tm_mday);
EXPECT_EQ(actual.tm_mon, expected.tm_mon);
EXPECT_EQ(actual.tm_year, expected.tm_year);
EXPECT_EQ(actual.tm_wday, expected.tm_wday);
EXPECT_EQ(actual.tm_yday, expected.tm_yday);
EXPECT_EQ(actual.tm_isdst, expected.tm_isdst);
}
template <typename TimePoint>
auto strftime_full_utc(TimePoint tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(tp);
auto tm = *std::gmtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
template <typename Time> void test_time(Time time) {
EXPECT_EQ(fmt::format("{}", time), "1979-03-12 12:00:00");
EXPECT_EQ(fmt::format("{:}", time), "1979-03-12 12:00:00");
EXPECT_EQ(fmt::format("{:%%}", time), "%");
EXPECT_EQ(fmt::format("{:%n}", time), "\n");
EXPECT_EQ(fmt::format("{:%t}", time), "\t");
EXPECT_EQ(fmt::format("{:%Y}", time), "1979");
EXPECT_EQ(fmt::format("{:%EY}", time), "1979");
EXPECT_EQ(fmt::format("{:%y}", time), "79");
EXPECT_EQ(fmt::format("{:%Oy}", time), "79");
EXPECT_EQ(fmt::format("{:%Ey}", time), "79");
EXPECT_EQ(fmt::format("{:%C}", time), "19");
EXPECT_EQ(fmt::format("{:%EC}", time), "19");
EXPECT_EQ(fmt::format("{:%G}", time), "1979");
EXPECT_EQ(fmt::format("{:%g}", time), "79");
EXPECT_EQ(fmt::format("{:%b}", time), "Mar");
EXPECT_EQ(fmt::format("{:%h}", time), "Mar");
EXPECT_EQ(fmt::format("{:%B}", time), "March");
EXPECT_EQ(fmt::format("{:%m}", time), "03");
EXPECT_EQ(fmt::format("{:%Om}", time), "03");
EXPECT_EQ(fmt::format("{:%U}", time), "10");
EXPECT_EQ(fmt::format("{:%OU}", time), "10");
EXPECT_EQ(fmt::format("{:%W}", time), "11");
EXPECT_EQ(fmt::format("{:%OW}", time), "11");
EXPECT_EQ(fmt::format("{:%V}", time), "11");
EXPECT_EQ(fmt::format("{:%OV}", time), "11");
EXPECT_EQ(fmt::format("{:%j}", time), "071");
EXPECT_EQ(fmt::format("{:%d}", time), "12");
EXPECT_EQ(fmt::format("{:%Od}", time), "12");
EXPECT_EQ(fmt::format("{:%e}", time), "12");
EXPECT_EQ(fmt::format("{:%Oe}", time), "12");
EXPECT_EQ(fmt::format("{:%a}", time), "Mon");
EXPECT_EQ(fmt::format("{:%A}", time), "Monday");
EXPECT_EQ(fmt::format("{:%w}", time), "1");
EXPECT_EQ(fmt::format("{:%Ow}", time), "1");
EXPECT_EQ(fmt::format("{:%u}", time), "1");
EXPECT_EQ(fmt::format("{:%Ou}", time), "1");
EXPECT_EQ(fmt::format("{:%H}", time), "12");
EXPECT_EQ(fmt::format("{:%OH}", time), "12");
EXPECT_EQ(fmt::format("{:%I}", time), "12");
EXPECT_EQ(fmt::format("{:%OI}", time), "12");
EXPECT_EQ(fmt::format("{:%M}", time), "00");
EXPECT_EQ(fmt::format("{:%OM}", time), "00");
EXPECT_EQ(fmt::format("{:%S}", time), "00");
EXPECT_EQ(fmt::format("{:%OS}", time), "00");
EXPECT_EQ(fmt::format("{:%x}", time), "03/12/79");
EXPECT_EQ(fmt::format("{:%Ex}", time), "03/12/79");
EXPECT_EQ(fmt::format("{:%X}", time), "12:00:00");
EXPECT_EQ(fmt::format("{:%EX}", time), "12:00:00");
EXPECT_EQ(fmt::format("{:%D}", time), "03/12/79");
EXPECT_EQ(fmt::format("{:%F}", time), "1979-03-12");
EXPECT_EQ(fmt::format("{:%R}", time), "12:00");
EXPECT_EQ(fmt::format("{:%T}", time), "12:00:00");
EXPECT_EQ(fmt::format("{:%p}", time), "PM");
EXPECT_EQ(fmt::format("{:%c}", time), "Mon Mar 12 12:00:00 1979");
EXPECT_EQ(fmt::format("{:%Ec}", time), "Mon Mar 12 12:00:00 1979");
EXPECT_EQ(fmt::format("{:%r}", time), "12:00:00 PM");
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", time), "1979-03-12 12:00:00");
}
TEST(chrono_test, system_clock_time_point) {
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{}", t1));
EXPECT_EQ(strftime_full_utc(t1), fmt::format("{:}", t1));
auto t2 = sys_time<std::chrono::seconds>(std::chrono::seconds(42));
EXPECT_EQ(strftime_full_utc(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p"};
#ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d",
"%a", "%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p"};
#endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
TEST(chrono_test, sys_time) {
auto time =
fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
test_time(time);
EXPECT_EQ(fmt::format("{:%z}", time), "+0000");
EXPECT_EQ(fmt::format("{:%Ez}", time), "+00:00");
EXPECT_EQ(fmt::format("{:%Oz}", time), "+00:00");
EXPECT_EQ(fmt::format("{:%Z}", time), "UTC");
}
// Timezone formatters tests makes sense for localtime.
#if FMT_HAS_C99_STRFTIME
spec_list = {"%z", "%Z"};
#else
spec_list = {"%Z"};
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
TEST(chrono_test, local_time) {
auto time =
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(290088000));
test_time(time);
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
fmt::format_error, "no timezone");
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
fmt::format_error, "no timezone");
}
auto sys_output = system_strftime(spec, &tm);
template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
auto set_tm_gmtoff(T& time, long offset) -> bool {
time.tm_gmtoff = offset;
return true;
}
template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
auto set_tm_gmtoff(T&, long) -> bool {
return false;
}
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
if (spec == "%z") {
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
TEST(chrono_test, tm) {
auto time = fmt::gmtime(290088000);
test_time(time);
if (set_tm_gmtoff(time, -28800)) {
EXPECT_EQ(fmt::format(fmt::runtime("{:%z}"), time), "-0800");
EXPECT_EQ(fmt::format(fmt::runtime("{:%Ez}"), time), "-08:00");
EXPECT_EQ(fmt::format(fmt::runtime("{:%Oz}"), time), "-08:00");
} else {
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%z}"), time),
fmt::format_error, "no timezone");
}
char tz[] = "EET";
if (fmt::detail::set_tm_zone(time, tz)) {
EXPECT_EQ(fmt::format(fmt::runtime("{:%Z}"), time), "EET");
} else {
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:%Z}"), time),
fmt::format_error, "no timezone");
}
}
// Separate tests for UTC, since std::time_put can use local time and ignoring
// the timezone in std::tm (if it presents on platform).
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
std::vector<std::string> tz_names = {"GMT", "UTC"};
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", t1)));
EXPECT_THAT(tz_names, Contains(fmt::format("{:%Z}", tm)));
TEST(chrono_test, daylight_savings_time_end) {
// 2024-10-27 03:05 as the number of seconds since epoch in Europe/Kyiv time.
// It is slightly after the DST end and passing it to to_sys will result in
// an ambiguous time error:
// 2024-10-27 03:05:00 is ambiguous. It could be
// 2024-10-27 03:05:00 EEST == 2024-10-27 00:05:00 UTC or
// 2024-10-27 03:05:00 EET == 2024-10-27 01:05:00 UTC
auto t =
fmt::local_time<std::chrono::seconds>(std::chrono::seconds(1729998300));
EXPECT_EQ(fmt::format("{}", t), "2024-10-27 03:05:00");
}
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
EXPECT_EQ(fmt::format("{:%z}", t1), "+0000");
EXPECT_EQ(fmt::format("{:%z}", tm), "+0000");
EXPECT_EQ(fmt::format("{:%Ez}", t1), "+00:00");
EXPECT_EQ(fmt::format("{:%Ez}", tm), "+00:00");
EXPECT_EQ(fmt::format("{:%Oz}", t1), "+00:00");
EXPECT_EQ(fmt::format("{:%Oz}", tm), "+00:00");
}
}
#if FMT_USE_LOCAL_TIME
TEST(chrono_test, localtime) {
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
EXPECT_TRUE(equal(tm, fmt::localtime(t)));
}
template <typename Duration>
auto strftime_full_local(std::chrono::local_time<Duration> tp) -> std::string {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(tp));
auto tm = *std::localtime(&t);
return system_strftime("%Y-%m-%d %H:%M:%S", &tm);
}
TEST(chrono_test, local_system_clock_time_point) {
# ifdef _WIN32
return; // Not supported on Windows.
# endif
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::current_zone()->to_local(std::chrono::system_clock::now()));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:%Y-%m-%d %H:%M:%S}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{}", t1));
EXPECT_EQ(strftime_full_local(t1), fmt::format("{:}", t1));
using time_point = std::chrono::local_time<std::chrono::seconds>;
auto t2 = time_point(std::chrono::seconds(86400 + 42));
EXPECT_EQ(strftime_full_local(t2), fmt::format("{:%Y-%m-%d %H:%M:%S}", t2));
std::vector<std::string> spec_list = {
"%%", "%n", "%t", "%Y", "%EY", "%y", "%Oy", "%Ey", "%C",
"%EC", "%G", "%g", "%b", "%h", "%B", "%m", "%Om", "%U",
"%OU", "%W", "%OW", "%V", "%OV", "%j", "%d", "%Od", "%e",
"%Oe", "%a", "%A", "%w", "%Ow", "%u", "%Ou", "%H", "%OH",
"%I", "%OI", "%M", "%OM", "%S", "%OS", "%x", "%Ex", "%X",
"%EX", "%D", "%F", "%R", "%T", "%p", "%z", "%Z"};
# ifndef _WIN32
// Disabled on Windows because these formats are not consistent among
// platforms.
spec_list.insert(spec_list.end(), {"%c", "%Ec", "%r"});
# elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {"%%", "%Y", "%y", "%b", "%B", "%m", "%U", "%W", "%j", "%d", "%a",
"%A", "%w", "%H", "%I", "%M", "%S", "%x", "%X", "%p", "%Z"};
# endif
spec_list.push_back("%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime(spec, &tm);
auto fmt_spec = fmt::format("{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
if (std::find(spec_list.cbegin(), spec_list.cend(), "%z") !=
spec_list.cend()) {
auto t = std::chrono::system_clock::to_time_t(
std::chrono::current_zone()->to_sys(t1));
auto tm = *std::localtime(&t);
auto sys_output = system_strftime("%z", &tm);
sys_output.insert(sys_output.end() - 2, 1, ':');
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", t1));
EXPECT_EQ(sys_output, fmt::format("{:%Oz}", tm));
}
}
#endif // FMT_USE_LOCAL_TIME
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
TEST(chrono_test, format_default) {
EXPECT_EQ(fmt::format("{}", std::chrono::seconds(42)), "42s");
EXPECT_EQ(fmt::format("{}", std::chrono::duration<int, std::atto>(42)),
@ -766,8 +713,8 @@ TEST(chrono_test, weekday) {
if (loc != std::locale::classic()) {
auto saturdays = std::vector<std::string>{"sáb", "sá.", "sáb."};
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:%a}", tm)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", sat)));
EXPECT_THAT(saturdays, Contains(fmt::format(loc, "{:L%a}", tm)));
}
}
@ -817,11 +764,11 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
EXPECT_EQ(fmt::format("{:.6%H:%M:%S}", dur), "01:00:01.234000");
}
using nanoseconds_dbl = std::chrono::duration<double, std::nano>;
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{-123456789}), "-00.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{9123456789}), "09.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(-123456789)), "-00.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(9123456789)), "09.123456789");
// Verify that only the seconds part is extracted and printed.
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123456789}), "39.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl{99123000000}), "39.123000000");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123456789)), "39.123456789");
EXPECT_EQ(fmt::format("{:%S}", nanoseconds_dbl(99123000000)), "39.123000000");
{
// Now the hour is printed, and we also test if negative doubles work.
auto dur = nanoseconds_dbl{-99123456789};
@ -832,7 +779,7 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
}
// Check that durations with precision greater than std::chrono::seconds have
// fixed precision, and print zeros even if there is no fractional part.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds{7000000}),
EXPECT_EQ(fmt::format("{:%S}", std::chrono::microseconds(7000000)),
"07.000000");
EXPECT_EQ(fmt::format("{:%S}",
std::chrono::duration<long long, std::ratio<1, 3>>(1)),
@ -852,14 +799,12 @@ TEST(chrono_test, cpp20_duration_subsecond_support) {
"-05:27.68");
// Check that floating point seconds with ratio<1,1> are printed.
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>{1.5}),
EXPECT_EQ(fmt::format("{:%S}", std::chrono::duration<double>(1.5)),
"01.500000");
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>{-61.25}),
EXPECT_EQ(fmt::format("{:%M:%S}", std::chrono::duration<double>(-61.25)),
"-01:01.250000");
}
#endif // FMT_STATIC_THOUSANDS_SEPARATOR
// Disable the utc_clock test for windows, as the icu.dll used for tzdb
// (time zone database) is not shipped with many windows versions.
#if FMT_USE_UTC_TIME && !defined(_WIN32)
@ -932,19 +877,11 @@ TEST(chrono_test, timestamp_sub_seconds) {
auto t8 =
sys_time<std::chrono::nanoseconds>(std::chrono::nanoseconds(123456789));
EXPECT_EQ(fmt::format("{:%S}", t8), "00.123456789");
EXPECT_EQ(fmt::format("{:%T}", t8), "00:00:00.123456789");
auto t9 = std::chrono::time_point_cast<std::chrono::nanoseconds>(
std::chrono::system_clock::now());
auto t9_sec = std::chrono::time_point_cast<std::chrono::seconds>(t9);
auto t9_sub_sec_part = fmt::format("{0:09}", (t9 - t9_sec).count());
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
fmt::format("{:%Y-%m-%d %H:%M:%S}", t9));
EXPECT_EQ(fmt::format("{}.{}", strftime_full_utc(t9_sec), t9_sub_sec_part),
fmt::format("{:%Y-%m-%d %T}", t9));
auto t10 =
auto t9 =
sys_time<std::chrono::milliseconds>(std::chrono::milliseconds(2000));
EXPECT_EQ(fmt::format("{:%S}", t10), "02.000");
EXPECT_EQ(fmt::format("{:%S}", t9), "02.000");
auto epoch = sys_time<std::chrono::milliseconds>();
auto d = std::chrono::milliseconds(250);
@ -1045,8 +982,6 @@ TEST(chrono_test, glibc_extensions) {
EXPECT_EQ(fmt::format("{:%_m}", t), " 7");
EXPECT_EQ(fmt::format("{:%-m}", t), "7");
}
}
TEST(chrono_test, out_of_range) {
@ -1083,6 +1018,6 @@ TEST(chrono_test, year_month_day) {
if (loc != std::locale::classic()) {
auto months = std::vector<std::string>{"ene.", "ene"};
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L}", month)));
EXPECT_THAT(months, Contains(fmt::format(loc, "{:%b}", month)));
EXPECT_THAT(months, Contains(fmt::format(loc, "{:L%b}", month)));
}
}

View File

@ -9,11 +9,68 @@
#include <iterator> // std::back_inserter
#include "gtest-extra.h" // EXPECT_WRITE
#include "gtest-extra.h" // EXPECT_WRITE, EXPECT_THROW_MSG
TEST(color_test, text_style) {
EXPECT_FALSE(fmt::text_style().has_foreground());
EXPECT_FALSE(fmt::text_style().has_background());
EXPECT_FALSE(fmt::text_style().has_emphasis());
EXPECT_TRUE(fg(fmt::rgb(0)).has_foreground());
EXPECT_FALSE(fg(fmt::rgb(0)).has_background());
EXPECT_FALSE(fg(fmt::rgb(0)).has_emphasis());
EXPECT_TRUE(bg(fmt::rgb(0)).has_background());
EXPECT_FALSE(bg(fmt::rgb(0)).has_foreground());
EXPECT_FALSE(bg(fmt::rgb(0)).has_emphasis());
EXPECT_TRUE(
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_foreground());
EXPECT_TRUE(
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_background());
EXPECT_FALSE(
(fg(fmt::rgb(0xFFFFFF)) | bg(fmt::rgb(0xFFFFFF))).has_emphasis());
EXPECT_EQ(fg(fmt::rgb(0x000000)) | fg(fmt::rgb(0x000000)),
fg(fmt::rgb(0x000000)));
EXPECT_EQ(fg(fmt::rgb(0x00000F)) | fg(fmt::rgb(0x00000F)),
fg(fmt::rgb(0x00000F)));
EXPECT_EQ(fg(fmt::rgb(0xC0F000)) | fg(fmt::rgb(0x000FEE)),
fg(fmt::rgb(0xC0FFEE)));
EXPECT_THROW_MSG(
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(
fg(fmt::terminal_color::black) | fg(fmt::terminal_color::white),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(
bg(fmt::terminal_color::black) | bg(fmt::terminal_color::white),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(fg(fmt::terminal_color::black) | fg(fmt::color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_THROW_MSG(bg(fmt::terminal_color::black) | bg(fmt::color::black),
fmt::format_error, "can't OR a terminal color");
EXPECT_NO_THROW(fg(fmt::terminal_color::white) |
bg(fmt::terminal_color::white));
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | bg(fmt::rgb(0xFFFFFF)));
EXPECT_NO_THROW(fg(fmt::terminal_color::white) | fmt::text_style());
EXPECT_NO_THROW(bg(fmt::terminal_color::white) | fmt::text_style());
}
TEST(color_test, format) {
EXPECT_EQ(fmt::format(fmt::text_style(), "no style"), "no style");
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 20, 30)), "rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::rgb(255, 0, 0)) | fg(fmt::rgb(0, 20, 30)),
"rgb(255,20,30)"),
"\x1b[38;2;255;020;030mrgb(255,20,30)\x1b[0m");
EXPECT_EQ(
fmt::format(fg(fmt::rgb(0, 0, 0)) | fg(fmt::rgb(0, 0, 0)), "rgb(0,0,0)"),
"\x1b[38;2;000;000;000mrgb(0,0,0)\x1b[0m");
EXPECT_EQ(fmt::format(fg(fmt::color::blue), "blue"),
"\x1b[38;2;000;000;255mblue\x1b[0m");
EXPECT_EQ(
@ -56,6 +113,15 @@ TEST(color_test, format) {
EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) |
fmt::emphasis::underline)),
"\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m");
EXPECT_EQ(
fmt::format(
"{}", fmt::styled(
"all", fmt::emphasis::bold | fmt::emphasis::faint |
fmt::emphasis::italic |
fmt::emphasis::underline | fmt::emphasis::blink |
fmt::emphasis::reverse | fmt::emphasis::conceal |
fmt::emphasis::strikethrough)),
"\x1b[1;2;3;4;5;7;8;9mall\x1b[0m");
}
TEST(color_test, format_to) {

View File

@ -1,61 +0,0 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/compile.h"
#include "gmock/gmock.h"
#if FMT_USE_CONSTEVAL
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
};
template <size_t max_string_length, typename Char = char, typename... Args>
consteval auto test_format(auto format, const Args&... args) {
test_string<max_string_length, Char> string{};
fmt::format_to(string.buffer, format, args...);
return string;
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif // FMT_USE_CONSTEVAL

View File

@ -7,6 +7,8 @@
#include "fmt/compile.h"
#include <iterator>
#include <list>
#include <type_traits>
#include <vector>
@ -88,9 +90,6 @@ TEST(compile_test, format_escape) {
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
}
TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@ -122,7 +121,6 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
}
TEST(compile_test, named) {
@ -131,10 +129,6 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar")));
@ -199,6 +193,21 @@ TEST(compile_test, format_to_n) {
EXPECT_STREQ("2a", buffer);
}
TEST(compile_test, output_iterators) {
std::list<char> out;
fmt::format_to(std::back_inserter(out), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", std::string(out.begin(), out.end()));
std::stringstream s;
fmt::format_to(std::ostream_iterator<char>(s), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", s.str());
std::stringstream s2;
fmt::format_to(std::ostreambuf_iterator<char>(s2), FMT_COMPILE("{}.{:06d}"),
42, 43);
EXPECT_EQ("42.000043", s2.str());
}
# if FMT_USE_CONSTEVAL && (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940)
TEST(compile_test, constexpr_formatted_size) {
FMT_CONSTEXPR20 size_t size = fmt::formatted_size(FMT_COMPILE("{}"), 42);
@ -219,6 +228,12 @@ TEST(compile_test, constexpr_formatted_size) {
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
EXPECT_EQ(str_size, 3);
}
TEST(compile_test, static_format) {
constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
EXPECT_STREQ(result.c_str(), "42");
EXPECT_EQ(result.str(), "42");
}
# endif
TEST(compile_test, text_and_arg) {
@ -295,7 +310,17 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
}
#endif
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename S> auto check_is_compiled_string(const S&) -> bool {
return fmt::is_compiled_string<S>::value;
}
TEST(compile_test, is_compiled_string) {
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("asdf")));
EXPECT_TRUE(check_is_compiled_string(FMT_COMPILE("{}")));
}
#endif
@ -310,7 +335,7 @@ TEST(compile_test, compile_format_string_literal) {
(FMT_MSC_VERSION >= 1928 && FMT_MSC_VERSION < 1930)) && \
defined(__cpp_lib_is_constant_evaluated)
template <size_t max_string_length, typename Char = char> struct test_string {
template <typename T> constexpr bool operator==(const T& rhs) const noexcept {
template <typename T> constexpr auto operator==(const T& rhs) const -> bool {
return fmt::basic_string_view<Char>(rhs).compare(buffer) == 0;
}
Char buffer[max_string_length]{};
@ -392,4 +417,53 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
}
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif
#if FMT_USE_CONSTEXPR_STRING
TEST(compile_test, constexpr_string_format) {
constexpr auto result = []() {
return fmt::format(FMT_COMPILE("{}"), 42) == "42";
}();
EXPECT_TRUE(result);
// Test with a larger string to avoid small string optimization.
constexpr auto big = []() {
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
}();
EXPECT_TRUE(big);
}
#endif // FMT_USE_CONSTEXPR_STRING

View File

@ -292,7 +292,7 @@ struct double_double {
auto format_as(double_double d) -> double { return d; }
bool operator>=(const double_double& lhs, const double_double& rhs) {
auto operator>=(const double_double& lhs, const double_double& rhs) -> bool {
return lhs.a + lhs.b >= rhs.a + rhs.b;
}
@ -307,19 +307,20 @@ struct slow_float {
auto format_as(slow_float f) -> float { return f; }
namespace std {
template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct numeric_limits<double_double> {
// is_iec559 is true for double-double in libstdc++.
static constexpr bool is_iec559 = true;
static constexpr int digits = 106;
static constexpr int digits10 = 33;
};
template <> struct is_floating_point<slow_float> : std::true_type {};
template <> struct numeric_limits<slow_float> : numeric_limits<float> {};
} // namespace std
FMT_BEGIN_NAMESPACE
namespace detail {
template <> struct is_floating_point<double_double> : std::true_type {};
template <> struct is_floating_point<slow_float> : std::true_type {};
template <> struct is_fast_float<slow_float> : std::false_type {};
namespace dragonbox {
template <> struct float_info<slow_float> {
@ -341,7 +342,7 @@ TEST(format_impl_test, write_dragon_even) {
auto s = std::string();
fmt::detail::write<char>(std::back_inserter(s), slow_float(33554450.0f), {});
// Specializing is_floating_point is broken in MSVC.
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "33554450");
if (!FMT_MSC_VERSION) EXPECT_EQ(s, "3.355445e+07");
}
#if defined(_WIN32) && !defined(FMT_USE_WRITE_CONSOLE)
@ -355,11 +356,11 @@ TEST(format_impl_test, write_console_signature) {
// A public domain branchless UTF-8 decoder by Christopher Wellons:
// https://github.com/skeeto/branchless-utf8
constexpr bool unicode_is_surrogate(uint32_t c) {
constexpr auto unicode_is_surrogate(uint32_t c) -> bool {
return c >= 0xD800U && c <= 0xDFFFU;
}
FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
FMT_CONSTEXPR auto utf8_encode(char* s, uint32_t c) -> char* {
if (c >= (1UL << 16)) {
s[0] = static_cast<char>(0xf0 | (c >> 18));
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));

View File

@ -206,10 +206,6 @@ TEST(util_test, parse_nonnegative_int) {
EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
}
TEST(format_impl_test, compute_width) {
EXPECT_EQ(fmt::detail::compute_width("вожык"), 5);
}
TEST(util_test, utf8_to_utf16) {
auto u = fmt::detail::utf8_to_utf16("лошадка");
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
@ -319,18 +315,17 @@ TEST(memory_buffer_test, move_ctor_inline_buffer) {
std::allocator<char>* alloc = buffer.get_allocator().get();
basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
// Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(5u, buffer2.capacity());
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
// Move should transfer allocator.
EXPECT_EQ(nullptr, buffer.get_allocator().get());
EXPECT_EQ(alloc, buffer2.get_allocator().get());
EXPECT_EQ(buffer.get_allocator().get(), nullptr);
EXPECT_EQ(buffer2.get_allocator().get(), alloc);
};
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
const char test[] = "test";
buffer.append(string_view(test, 4));
buffer.append(string_view("test"));
check_move_buffer("test", buffer);
// Adding one more character fills the inline buffer, but doesn't cause
// dynamic allocation.
@ -355,14 +350,63 @@ TEST(memory_buffer_test, move_ctor_dynamic_buffer) {
EXPECT_GT(buffer2.capacity(), 4u);
}
using std_allocator_noprop = allocator_ref<std::allocator<char>, false>;
TEST(memory_buffer_test, move_ctor_inline_buffer_non_propagating) {
auto check_move_buffer =
[](const char* str,
basic_memory_buffer<char, 5, std_allocator_noprop>& buffer) {
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
const char* original_data_ptr = &buffer[0];
basic_memory_buffer<char, 5, std_allocator_noprop> buffer2(
std::move(buffer));
const char* new_data_ptr = &buffer2[0];
EXPECT_NE(new_data_ptr, original_data_ptr);
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
// Allocators should NOT be transferred; they remain distinct instances.
// The original buffer's allocator pointer should still be valid (not
// nullptr).
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
};
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
buffer.append(string_view("test", 4));
check_move_buffer("test", buffer);
buffer.push_back('a');
check_move_buffer("testa", buffer);
}
TEST(memory_buffer_test, move_ctor_dynamic_buffer_non_propagating) {
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
const char test[] = "test";
buffer.append(test, test + 4);
const char* inline_buffer_ptr = &buffer[0];
buffer.push_back('a');
EXPECT_NE(buffer.data(), inline_buffer_ptr);
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer2;
buffer2 = std::move(buffer);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testa");
EXPECT_GT(buffer2.capacity(), 4u);
EXPECT_NE(buffer2.data(), inline_buffer_ptr);
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
}
void check_move_assign_buffer(const char* str,
basic_memory_buffer<char, 5>& buffer) {
basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer);
// Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(5u, buffer2.capacity());
EXPECT_EQ(std::string(&buffer[0], buffer.size()), str);
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
}
TEST(memory_buffer_test, move_assignment) {
@ -381,8 +425,8 @@ TEST(memory_buffer_test, move_assignment) {
basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer);
// Move should rip the guts of the first buffer.
EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(buffer.data(), inline_buffer_ptr);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testab");
EXPECT_GT(buffer2.capacity(), 5u);
}
@ -481,6 +525,12 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
EXPECT_THROW(buffer.resize(161), std::exception);
}
TEST(memory_buffer_test, back_insert_iterator) {
fmt::memory_buffer buf;
using iterator = decltype(std::back_inserter(buf));
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<iterator>::value);
}
TEST(format_test, digits2_alignment) {
auto p =
fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0));
@ -552,6 +602,10 @@ TEST(format_test, arg_errors) {
format_error, "argument not found");
}
TEST(format_test, display_width_precision) {
EXPECT_EQ(fmt::format("{:.5}", "🐱🐱🐱"), "🐱🐱");
}
template <int N> struct test_format {
template <typename... T>
static auto format(fmt::string_view fmt, const T&... args) -> std::string {
@ -582,6 +636,9 @@ TEST(format_test, named_arg) {
EXPECT_EQ("1/a/A", fmt::format("{_1}/{a_}/{A_}", fmt::arg("a_", 'a'),
fmt::arg("A_", "A"), fmt::arg("_1", 1)));
EXPECT_EQ(fmt::format("{0:{width}}", -42, fmt::arg("width", 4)), " -42");
EXPECT_EQ(fmt::format("{value:{width}}", fmt::arg("value", -42),
fmt::arg("width", 4)),
" -42");
EXPECT_EQ("st",
fmt::format("{0:.{precision}}", "str", fmt::arg("precision", 2)));
EXPECT_EQ(fmt::format("{} {two}", 1, fmt::arg("two", 2)), "1 2");
@ -599,6 +656,9 @@ TEST(format_test, named_arg) {
EXPECT_THROW_MSG((void)fmt::format(runtime("{a} {}"), fmt::arg("a", 2), 42),
format_error,
"cannot switch from manual to automatic argument indexing");
EXPECT_THROW_MSG(
(void)fmt::format("{a}", fmt::arg("a", 1), fmt::arg("a", 10)),
format_error, "duplicate named arg");
}
TEST(format_test, auto_arg_index) {
@ -871,6 +931,7 @@ TEST(format_test, width) {
" 0xcafe");
EXPECT_EQ(fmt::format("{:11}", 'x'), "x ");
EXPECT_EQ(fmt::format("{:12}", "str"), "str ");
EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "*🤡**");
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
@ -878,6 +939,31 @@ TEST(format_test, width) {
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
}
TEST(format_test, debug_presentation) {
EXPECT_EQ(fmt::format("{:?}", ""), R"("")");
EXPECT_EQ(fmt::format("{:*<5.0?}", "\n"), R"(*****)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "\n"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "\n"), R"("\***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "\n"), R"("\n**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", "\n"), R"("\n"*)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "Σ"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "Σ"), R"("Σ***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "Σ"), R"("Σ"**)");
EXPECT_EQ(fmt::format("{:*<5.1?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.3?}", ""), R"("**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", ""), R"(""*)");
EXPECT_EQ(fmt::format("{:*<8?}", "туда"), R"("туда"**)");
EXPECT_EQ(fmt::format("{:*>8?}", "сюда"), R"(**"сюда")");
EXPECT_EQ(fmt::format("{:*^8?}", "中心"), R"(*""*)");
EXPECT_EQ(fmt::format("{:*^14?}", "A\t👈🤯ы猫"), R"(*"A\t👈🤯ы"*)");
}
auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES
? "width/precision is out of range"
: "width/precision is not integer";
@ -1068,7 +1154,8 @@ TEST(format_test, precision) {
EXPECT_EQ(fmt::format("{:#.0f}", 123.0), "123.");
EXPECT_EQ(fmt::format("{:.02f}", 1.234), "1.23");
EXPECT_EQ(fmt::format("{:.1g}", 0.001), "0.001");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1019666400");
EXPECT_EQ(fmt::format("{}", 123456789.0f), "1.2345679e+08");
EXPECT_EQ(fmt::format("{}", 1019666432.0f), "1.0196664e+09");
EXPECT_EQ(fmt::format("{:.0e}", 9.5), "1e+01");
EXPECT_EQ(fmt::format("{:.1e}", 1e-34), "1.0e-34");
@ -1078,9 +1165,6 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
EXPECT_THROW_MSG(
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
format_error, "number is too big");
@ -1092,9 +1176,34 @@ TEST(format_test, precision) {
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
}
TEST(format_test, large_precision) {
// Iterator used to abort the actual output.
struct throwing_iterator {
auto operator=(char) -> throwing_iterator& {
throw std::runtime_error("aborted");
return *this;
}
auto operator*() -> throwing_iterator& { return *this; }
auto operator++() -> throwing_iterator& { return *this; }
auto operator++(int) -> throwing_iterator { return *this; }
};
auto it = throwing_iterator();
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}}"), 1.0,
fmt::detail::max_value<int>()),
std::runtime_error, "aborted");
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}e}"), 1.0,
fmt::detail::max_value<int>() - 1),
std::runtime_error, "aborted");
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
}
TEST(format_test, utf8_precision) {
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
EXPECT_EQ(fmt::detail::compute_width(result), 4);
EXPECT_EQ(result, "caf\u00e9");
}
@ -1748,19 +1857,15 @@ TEST(format_test, format_examples) {
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
EXPECT_EQ("The answer is 42.", to_string(out));
const char* filename = "nonexistent";
FILE* ftest = safe_fopen(filename, "r");
if (ftest) fclose(ftest);
int error_code = errno;
EXPECT_TRUE(ftest == nullptr);
EXPECT_SYSTEM_ERROR(
EXPECT_THROW(
{
FILE* f = safe_fopen(filename, "r");
if (!f)
throw fmt::system_error(errno, "Cannot open file '{}'", filename);
fclose(f);
const char* filename = "madeup";
FILE* file = fopen(filename, "r");
if (!file)
throw fmt::system_error(errno, "cannot open file '{}'", filename);
fclose(file);
},
error_code, "Cannot open file 'nonexistent'");
std::system_error);
EXPECT_EQ("First, thou shalt count to three",
fmt::format("First, thou shalt count to {0}", "three"));
@ -1820,54 +1925,6 @@ TEST(format_test, big_print) {
EXPECT_WRITE(stdout, big_print(), std::string(count, 'x'));
}
// Windows CRT implements _IOLBF incorrectly (full buffering).
#if FMT_USE_FCNTL
# ifndef _WIN32
TEST(format_test, line_buffering) {
auto pipe = fmt::pipe();
int write_fd = pipe.write_end.descriptor();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
write_end.print("42\n");
close(write_fd);
try {
write_end.close();
} catch (const std::system_error&) {
}
auto read_end = pipe.read_end.fdopen("r");
std::thread reader([&]() {
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
(void)result;
EXPECT_EQ(n, 42);
});
reader.join();
}
# endif
TEST(format_test, buffer_boundary) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
for (int i = 3; i < 4094; i++)
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
write_end.print("{} {}", 1234, 567);
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
char buf[4091] = {};
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
EXPECT_EQ(n, sizeof(buf));
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
}
#endif // FMT_USE_FCNTL
struct deadlockable {
int value = 0;
mutable std::mutex mutex;
@ -1979,11 +2036,6 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
}
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
static constexpr const char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
@ -1998,19 +2050,12 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
#endif
(void)static_with_null;
(void)static_no_null;
static constexpr char format_str[3] = {'{', '}', '\0'};
(void)format_str;
#ifndef _MSC_VER
EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42");
#endif
(void)with_null;
(void)no_null;
#if FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
#endif
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
#endif
@ -2102,6 +2147,10 @@ TEST(format_test, output_iterators) {
std::stringstream s;
fmt::format_to(std::ostream_iterator<char>(s), "{}", 42);
EXPECT_EQ("42", s.str());
std::stringstream s2;
fmt::format_to(std::ostreambuf_iterator<char>(s2), "{}.{:06d}", 42, 43);
EXPECT_EQ("42.000043", s2.str());
}
TEST(format_test, fill_via_appender) {
@ -2536,6 +2585,20 @@ TEST(format_test, writer) {
EXPECT_EQ(s.str(), "foo");
}
#if FMT_USE_FCNTL && !defined(_WIN32)
TEST(format_test, invalid_glibc_buffer) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
auto file = write_end.get();
// This results in _IO_write_ptr < _IO_write_end.
fprintf(file, "111\n");
setvbuf(file, nullptr, _IOLBF, 0);
fmt::print(file, "------\n");
}
#endif // FMT_USE_FCNTL
#if FMT_USE_BITINT
FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension")
@ -2579,3 +2642,26 @@ TEST(base_test, format_byte) {
EXPECT_EQ(s, "42");
}
#endif
// Only defined after the test case.
struct incomplete_type;
extern const incomplete_type& external_instance;
FMT_BEGIN_NAMESPACE
template <> struct formatter<incomplete_type> : formatter<int> {
auto format(const incomplete_type& x, context& ctx) const -> appender;
};
FMT_END_NAMESPACE
TEST(incomplete_type_test, format) {
EXPECT_EQ(fmt::format("{}", external_instance), "42");
}
struct incomplete_type {};
const incomplete_type& external_instance = {};
auto fmt::formatter<incomplete_type>::format(const incomplete_type&,
fmt::context& ctx) const
-> fmt::appender {
return formatter<int>::format(42, ctx);
}

View File

@ -12,10 +12,10 @@ void invoke_inner(fmt::string_view format_str, Rep rep) {
auto value = std::chrono::duration<Rep, Period>(rep);
try {
#if FMT_FUZZ_FORMAT_TO_STRING
std::string message = fmt::format(format_str, value);
std::string message = fmt::format(fmt::runtime(format_str), value);
#else
auto buf = fmt::memory_buffer();
fmt::format_to(std::back_inserter(buf), format_str, value);
fmt::format_to(std::back_inserter(buf), fmt::runtime(format_str), value);
#endif
} catch (std::exception&) {
}

View File

@ -22,7 +22,7 @@
#define FMT_FUZZ_SEPARATE_ALLOCATION 1
// The size of the largest possible type in use.
// To let the the fuzzer mutation be efficient at cross pollinating between
// To let the fuzzer mutation be efficient at cross pollinating between
// different types, use a fixed size format. The same bit pattern, interpreted
// as another type, is likely interesting.
constexpr auto fixed_size = 16;

View File

@ -15,6 +15,10 @@
#include "fmt/os.h"
#include "gmock/gmock.h"
#ifdef _MSC_VER
# include <crtdbg.h>
#endif
#define FMT_TEST_THROW_(statement, expected_exception, expected_message, fail) \
GTEST_AMBIGUOUS_ELSE_BLOCKER_ \
if (::testing::AssertionResult gtest_ar = ::testing::AssertionSuccess()) { \

View File

@ -37,7 +37,7 @@ template <typename T> class mock_allocator {
MOCK_METHOD(void, deallocate, (T*, size_t));
};
template <typename Allocator> class allocator_ref {
template <typename Allocator, bool PropagateOnMove = true> class allocator_ref {
private:
Allocator* alloc_;
@ -48,6 +48,8 @@ template <typename Allocator> class allocator_ref {
public:
using value_type = typename Allocator::value_type;
using propagate_on_container_move_assignment =
fmt::bool_constant<PropagateOnMove>;
explicit allocator_ref(Allocator* alloc = nullptr) : alloc_(alloc) {}
@ -66,12 +68,21 @@ template <typename Allocator> class allocator_ref {
}
public:
Allocator* get() const { return alloc_; }
auto get() const -> Allocator* { return alloc_; }
value_type* allocate(size_t n) {
auto allocate(size_t n) -> value_type* {
return std::allocator_traits<Allocator>::allocate(*alloc_, n);
}
void deallocate(value_type* p, size_t n) { alloc_->deallocate(p, n); }
friend auto operator==(allocator_ref a, allocator_ref b) noexcept -> bool {
if (a.alloc_ == b.alloc_) return true;
return a.alloc_ && b.alloc_ && *a.alloc_ == *b.alloc_;
}
friend auto operator!=(allocator_ref a, allocator_ref b) noexcept -> bool {
return !(a == b);
}
};
#endif // FMT_MOCK_ALLOCATOR_H_

View File

@ -13,6 +13,7 @@
TEST(no_builtin_types_test, format) {
EXPECT_EQ(fmt::format("{}", 42), "42");
EXPECT_EQ(fmt::format("{}", 42L), "42");
}
TEST(no_builtin_types_test, double_is_custom_type) {

View File

@ -10,6 +10,7 @@
#include <cstdlib> // std::exit
#include <cstring>
#include <memory>
#include <thread>
#include "gtest-extra.h"
#include "util.h"
@ -18,10 +19,21 @@ using fmt::buffered_file;
using testing::HasSubstr;
using wstring_view = fmt::basic_string_view<wchar_t>;
static std::string uniq_file_name(unsigned line_number) {
static auto uniq_file_name(unsigned line_number) -> std::string {
return "test-file" + std::to_string(line_number);
}
auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
#ifdef _WIN32
# include <windows.h>
@ -236,8 +248,7 @@ TEST(buffered_file_test, descriptor) {
}
TEST(ostream_test, move) {
auto test_file = uniq_file_name(__LINE__);
fmt::ostream out = fmt::output_file(test_file);
fmt::ostream out = fmt::output_file(uniq_file_name(__LINE__));
fmt::ostream moved(std::move(out));
moved.print("hello");
}
@ -429,8 +440,7 @@ TEST(file_test, read) {
}
TEST(file_test, read_error) {
auto test_file = uniq_file_name(__LINE__);
file f(test_file, file::WRONLY | file::CREATE);
file f(uniq_file_name(__LINE__), file::WRONLY | file::CREATE);
char buf;
// We intentionally read from a file opened in the write-only mode to
// cause error.
@ -439,15 +449,13 @@ TEST(file_test, read_error) {
TEST(file_test, write) {
auto pipe = fmt::pipe();
auto test_file = uniq_file_name(__LINE__);
write(pipe.write_end, test_file);
write(pipe.write_end, "test");
pipe.write_end.close();
EXPECT_READ(pipe.read_end, test_file);
EXPECT_READ(pipe.read_end, "test");
}
TEST(file_test, write_error) {
auto test_file = uniq_file_name(__LINE__);
file f(test_file, file::RDONLY | file::CREATE);
file f(uniq_file_name(__LINE__), file::RDONLY | file::CREATE);
// We intentionally write to a file opened in the read-only mode to
// cause error.
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");
@ -513,4 +521,79 @@ TEST(file_test, fdopen) {
int read_fd = pipe.read_end.descriptor();
EXPECT_EQ(read_fd, FMT_POSIX(fileno(pipe.read_end.fdopen("r").get())));
}
// Windows CRT implements _IOLBF incorrectly (full buffering).
# ifndef _WIN32
TEST(file_test, line_buffering) {
auto pipe = fmt::pipe();
int write_fd = pipe.write_end.descriptor();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOLBF, 4096);
write_end.print("42\n");
close(write_fd);
try {
write_end.close();
} catch (const std::system_error&) {
}
auto read_end = pipe.read_end.fdopen("r");
std::thread reader([&]() {
int n = 0;
int result = fscanf(read_end.get(), "%d", &n);
(void)result;
EXPECT_EQ(n, 42);
});
reader.join();
}
# endif // _WIN32
TEST(file_test, buffer_boundary) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
for (int i = 3; i < 4094; i++)
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
write_end.print("{} {}", 1234, 567);
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
char buf[4091] = {};
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
EXPECT_EQ(n, sizeof(buf));
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
}
TEST(file_test, io_putting) {
auto pipe = fmt::pipe();
auto read_end = pipe.read_end.fdopen("r");
auto write_end = pipe.write_end.fdopen("w");
size_t read_size = 0;
auto reader = std::thread([&]() {
size_t n = 0;
do {
char buf[4096] = {};
n = fread(buf, 1, sizeof(buf), read_end.get());
read_size += n;
} while (n != 0);
});
// This initialize buffers but doesn't set _IO_CURRENTLY_PUTTING.
fseek(write_end.get(), 0, SEEK_SET);
size_t write_size = 0;
for (int i = 0; i <= 20000; ++i) {
auto s = fmt::format("{}\n", i);
fmt::print(write_end.get(), "{}", s);
write_size += s.size();
}
write_end.close();
reader.join();
EXPECT_EQ(read_size, write_size);
}
#endif // FMT_USE_FCNTL

View File

@ -265,7 +265,7 @@ template <> struct formatter<abstract> : ostream_formatter {};
} // namespace fmt
void format_abstract_compiles(const abstract& a) {
fmt::format(FMT_COMPILE("{}"), a);
(void)fmt::format(FMT_COMPILE("{}"), a);
}
TEST(ostream_test, is_formattable) {

View File

@ -6,16 +6,12 @@
// For the license information refer to format.h.
#include "fmt/printf.h"
// include <format> if possible for https://github.com/fmtlib/fmt/pull/4042
#if FMT_HAS_INCLUDE(<format>) && FMT_CPLUSPLUS > 201703L
# include <format>
#endif
#include <cctype>
#include <climits>
#include <cstring>
#include "fmt/xchar.h"
#include "fmt/xchar.h" // DEPRECATED!
#include "gtest-extra.h"
#include "util.h"
@ -26,27 +22,21 @@ using fmt::detail::max_value;
const unsigned big_num = INT_MAX + 1u;
// Makes format string argument positional.
static std::string make_positional(fmt::string_view format) {
static auto make_positional(fmt::string_view format) -> std::string {
std::string s(format.data(), format.size());
s.replace(s.find('%'), 1, "%1$");
return s;
}
static std::wstring make_positional(fmt::basic_string_view<wchar_t> format) {
std::wstring s(format.data(), format.size());
s.replace(s.find(L'%'), 1, L"%1$");
return s;
}
// A wrapper around fmt::sprintf to workaround bogus warnings about invalid
// format strings in MSVC.
template <typename... Args>
std::string test_sprintf(fmt::string_view format, const Args&... args) {
auto test_sprintf(fmt::string_view format, const Args&... args) -> std::string {
return fmt::sprintf(format, args...);
}
template <typename... Args>
std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
const Args&... args) {
auto test_sprintf(fmt::basic_string_view<wchar_t> format, const Args&... args)
-> std::wstring {
return fmt::sprintf(format, args...);
}
@ -55,10 +45,7 @@ std::wstring test_sprintf(fmt::basic_string_view<wchar_t> format,
<< "format: " << format; \
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
TEST(printf_test, no_args) {
EXPECT_EQ("test", test_sprintf("test"));
EXPECT_EQ(L"test", fmt::sprintf(L"test"));
}
TEST(printf_test, no_args) { EXPECT_EQ("test", test_sprintf("test")); }
TEST(printf_test, escape) {
EXPECT_EQ("%", test_sprintf("%%"));
@ -66,11 +53,6 @@ TEST(printf_test, escape) {
EXPECT_EQ("% after", test_sprintf("%% after"));
EXPECT_EQ("before % after", test_sprintf("before %% after"));
EXPECT_EQ("%s", test_sprintf("%%s"));
EXPECT_EQ(L"%", fmt::sprintf(L"%%"));
EXPECT_EQ(L"before %", fmt::sprintf(L"before %%"));
EXPECT_EQ(L"% after", fmt::sprintf(L"%% after"));
EXPECT_EQ(L"before % after", fmt::sprintf(L"before %% after"));
EXPECT_EQ(L"%s", fmt::sprintf(L"%%s"));
}
TEST(printf_test, positional_args) {
@ -467,9 +449,6 @@ TEST(printf_test, char) {
EXPECT_PRINTF("x", "%c", 'x');
int max = max_value<int>();
EXPECT_PRINTF(fmt::format("{}", static_cast<char>(max)), "%c", max);
// EXPECT_PRINTF("x", "%lc", L'x');
EXPECT_PRINTF(L"x", L"%c", L'x');
EXPECT_PRINTF(fmt::format(L"{}", static_cast<wchar_t>(max)), L"%c", max);
}
TEST(printf_test, string) {
@ -477,10 +456,6 @@ TEST(printf_test, string) {
const char* null_str = nullptr;
EXPECT_PRINTF("(null)", "%s", null_str);
EXPECT_PRINTF(" (null)", "%10s", null_str);
EXPECT_PRINTF(L"abc", L"%s", L"abc");
const wchar_t* null_wstr = nullptr;
EXPECT_PRINTF(L"(null)", L"%s", null_wstr);
EXPECT_PRINTF(L" (null)", L"%10s", null_wstr);
}
TEST(printf_test, pointer) {
@ -494,16 +469,6 @@ TEST(printf_test, pointer) {
EXPECT_PRINTF(fmt::format("{:p}", s), "%p", s);
const char* null_str = nullptr;
EXPECT_PRINTF("(nil)", "%p", null_str);
p = &n;
EXPECT_PRINTF(fmt::format(L"{}", p), L"%p", p);
p = nullptr;
EXPECT_PRINTF(L"(nil)", L"%p", p);
EXPECT_PRINTF(L" (nil)", L"%10p", p);
const wchar_t* w = L"test";
EXPECT_PRINTF(fmt::format(L"{:p}", w), L"%p", w);
const wchar_t* null_wstr = nullptr;
EXPECT_PRINTF(L"(nil)", L"%p", null_wstr);
}
enum test_enum { answer = 42 };
@ -531,10 +496,6 @@ TEST(printf_test, printf_error) {
}
#endif
TEST(printf_test, wide_string) {
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
}
TEST(printf_test, vprintf) {
int n = 42;
auto store = fmt::make_format_args<fmt::printf_context>(n);

View File

@ -47,6 +47,8 @@ TEST(ranges_test, format_array_of_literals) {
}
#endif // FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
struct unformattable {};
TEST(ranges_test, format_vector) {
auto v = std::vector<int>{1, 2, 3, 5, 7, 11};
EXPECT_EQ(fmt::format("{}", v), "[1, 2, 3, 5, 7, 11]");
@ -65,6 +67,9 @@ TEST(ranges_test, format_vector) {
EXPECT_EQ(fmt::format("{:n}", vvc), "['a', 'b', 'c'], ['a', 'b', 'c']");
EXPECT_EQ(fmt::format("{:n:n}", vvc), "'a', 'b', 'c', 'a', 'b', 'c'");
EXPECT_EQ(fmt::format("{:n:n:}", vvc), "a, b, c, a, b, c");
EXPECT_FALSE(fmt::is_formattable<unformattable>::value);
EXPECT_FALSE(fmt::is_formattable<std::vector<unformattable>>::value);
}
TEST(ranges_test, format_nested_vector) {
@ -83,6 +88,8 @@ TEST(ranges_test, format_map) {
auto m = std::map<std::string, int>{{"one", 1}, {"two", 2}};
EXPECT_EQ(fmt::format("{}", m), "{\"one\": 1, \"two\": 2}");
EXPECT_EQ(fmt::format("{:n}", m), "\"one\": 1, \"two\": 2");
EXPECT_FALSE((fmt::is_formattable<std::map<int, unformattable>>::value));
}
struct test_map_value {};
@ -146,6 +153,7 @@ template <typename T> class flat_set {
TEST(ranges_test, format_flat_set) {
EXPECT_EQ(fmt::format("{}", flat_set<std::string>{"one", "two"}),
"{\"one\", \"two\"}");
EXPECT_FALSE(fmt::is_formattable<flat_set<unformattable>>::value);
}
namespace adl {
@ -169,8 +177,6 @@ TEST(ranges_test, format_pair) {
EXPECT_EQ(fmt::format("{:n}", p), "421.5");
}
struct unformattable {};
TEST(ranges_test, format_tuple) {
auto t =
std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
@ -180,7 +186,6 @@ TEST(ranges_test, format_tuple) {
EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
@ -227,9 +232,12 @@ auto get(const tuple_like& t) noexcept -> decltype(t.get<N>()) {
return t.get<N>();
}
// https://github.com/llvm/llvm-project/issues/39218
FMT_PRAGMA_CLANG(diagnostic ignored "-Wmismatched-tags")
namespace std {
template <>
struct tuple_size<tuple_like> : std::integral_constant<size_t, 2> {};
struct tuple_size<tuple_like> : public std::integral_constant<size_t, 2> {};
template <size_t N> struct tuple_element<N, tuple_like> {
using type = decltype(std::declval<tuple_like>().get<N>());
@ -322,7 +330,7 @@ template <typename T> class noncopyable_range {
explicit noncopyable_range(Args&&... args)
: vec(std::forward<Args>(args)...) {}
noncopyable_range(noncopyable_range const&) = delete;
noncopyable_range(const noncopyable_range&) = delete;
noncopyable_range(noncopyable_range&) = delete;
auto begin() -> iterator { return vec.begin(); }
@ -655,6 +663,8 @@ TEST(ranges_test, container_adaptor) {
m.push(2);
EXPECT_EQ(fmt::format("{}", m), "[1, 2]");
}
EXPECT_FALSE(fmt::is_formattable<std::stack<unformattable>>::value);
}
struct tieable {

View File

@ -229,6 +229,8 @@ enum class scan_type {
uint_type,
long_long_type,
ulong_long_type,
double_type,
float_type,
string_type,
string_view_type,
custom_type
@ -251,6 +253,8 @@ template <typename Context> class basic_scan_arg {
unsigned* uint_value_;
long long* long_long_value_;
unsigned long long* ulong_long_value_;
double* double_value_;
float* float_value_;
std::string* string_;
string_view* string_view_;
detail::custom_scan_arg<Context> custom_;
@ -276,6 +280,10 @@ template <typename Context> class basic_scan_arg {
: type_(scan_type::long_long_type), long_long_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(unsigned long long& value)
: type_(scan_type::ulong_long_type), ulong_long_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(double& value)
: type_(scan_type::double_type), double_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(float& value)
: type_(scan_type::float_type), float_value_(&value) {}
FMT_CONSTEXPR basic_scan_arg(std::string& value)
: type_(scan_type::string_type), string_(&value) {}
FMT_CONSTEXPR basic_scan_arg(string_view& value)
@ -305,6 +313,10 @@ template <typename Context> class basic_scan_arg {
return vis(*long_long_value_);
case scan_type::ulong_long_type:
return vis(*ulong_long_value_);
case scan_type::double_type:
return vis(*double_value_);
case scan_type::float_type:
return vis(*float_value_);
case scan_type::string_type:
return vis(*string_);
case scan_type::string_view_type:
@ -457,6 +469,47 @@ auto read(scan_iterator it, T& value, const format_specs& specs = {})
return it;
}
auto read(scan_iterator it, double& value, const format_specs& = {})
-> scan_iterator {
if (it == scan_sentinel()) return it;
// Simple floating-point parsing
bool negative = *it == '-';
if (negative) {
++it;
if (it == scan_sentinel()) report_error("invalid input");
}
double result = 0.0;
// Parse integer part
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
result = result * 10.0 + (*it - '0');
++it;
}
// Parse decimal part if present
if (it != scan_sentinel() && *it == '.') {
++it;
double fraction = 0.1;
while (it != scan_sentinel() && *it >= '0' && *it <= '9') {
result += (*it - '0') * fraction;
fraction *= 0.1;
++it;
}
}
value = negative ? -result : result;
return it;
}
auto read(scan_iterator it, float& value, const format_specs& specs = {})
-> scan_iterator {
double temp;
it = read(it, temp, specs);
value = static_cast<float>(temp);
return it;
}
auto read(scan_iterator it, std::string& value, const format_specs& = {})
-> scan_iterator {
while (it != scan_sentinel() && *it != ' ') value.push_back(*it++);

View File

@ -20,6 +20,11 @@
TEST(std_test, path) {
using std::filesystem::path;
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
// see #4303
const path p = "/usr/bin";
EXPECT_EQ(fmt::format("{}", p), "/usr/bin");
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
@ -34,16 +39,18 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD <20> TAIL");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")),
"HEAD \xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")),
"HEAD <20>\xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
"[\xED\xA0\xBD\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
# endif
}
// Intentionally delayed include to test #4303
# include "fmt/ranges.h"
// Test ambiguity problem described in #2954.
TEST(ranges_std_test, format_vector_path) {
auto p = std::filesystem::path("foo/bar.txt");
@ -91,6 +98,9 @@ TEST(std_test, complex) {
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "(1+2i) ");
EXPECT_EQ(fmt::format("{:-<8}", std::complex<double>(1, 2)), "(1+2i)--");
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
" (1.00+2.20i)");
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),
@ -135,6 +145,7 @@ TEST(std_test, optional) {
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif
}
@ -186,7 +197,33 @@ class my_class {
return fmt::to_string(elm.av);
}
};
class my_class_int {
public:
int av;
private:
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
};
} // namespace my_nso
TEST(std_test, expected_format_as) {
#ifdef __cpp_lib_expected
EXPECT_EQ(
fmt::format(
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
"expected(\"first\")");
EXPECT_EQ(
fmt::format("{}",
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
"expected(\"7\")");
EXPECT_EQ(fmt::format("{}",
std::expected<my_nso::my_class_int, int>{
my_nso::my_class_int{8}}),
"expected(8)");
#endif
}
TEST(std_test, optional_format_as) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
@ -195,6 +232,8 @@ TEST(std_test, optional_format_as) {
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
"optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif
}
@ -264,20 +303,42 @@ TEST(std_test, variant) {
#endif
}
TEST(std_test, variant_format_as) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
"variant(\"first\")");
EXPECT_EQ(fmt::format(
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
"variant(\"first\")");
EXPECT_EQ(
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
"variant(\"7\")");
EXPECT_EQ(
fmt::format("{}",
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
"variant(8)");
#endif
}
TEST(std_test, error_code) {
auto& generic = std::generic_category();
EXPECT_EQ("generic:42",
fmt::format(FMT_STRING("{0}"), std::error_code(42, generic)));
EXPECT_EQ(" generic:42",
fmt::format(FMT_STRING("{:>12}"), std::error_code(42, generic)));
EXPECT_EQ("generic:42 ",
fmt::format(FMT_STRING("{:12}"), std::error_code(42, generic)));
EXPECT_EQ("system:42",
fmt::format(FMT_STRING("{0}"),
std::error_code(42, fmt::system_category())));
EXPECT_EQ("system:-42",
fmt::format(FMT_STRING("{0}"),
std::error_code(-42, fmt::system_category())));
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
EXPECT_EQ(fmt::format("{:>12}", std::error_code(42, generic)),
" generic:42");
EXPECT_EQ(fmt::format("{:12}", std::error_code(42, generic)), "generic:42 ");
EXPECT_EQ(fmt::format("{}", std::error_code(42, fmt::system_category())),
"system:42");
EXPECT_EQ(fmt::format("{}", std::error_code(-42, fmt::system_category())),
"system:-42");
auto ec = std::make_error_code(std::errc::value_too_large);
EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
"\"generic:42\"");
EXPECT_EQ(fmt::format("{}",
std::map<std::error_code, int>{
{std::error_code(42, generic), 0}}),
"{\"generic:42\": 0}");
}
template <typename Catch> void exception_test() {
@ -373,11 +434,12 @@ TEST(std_test, format_atomic) {
#ifdef __cpp_lib_atomic_flag_test
TEST(std_test, format_atomic_flag) {
std::atomic_flag f = ATOMIC_FLAG_INIT;
std::atomic_flag f;
(void)f.test_and_set();
EXPECT_EQ(fmt::format("{}", f), "true");
const std::atomic_flag cf = ATOMIC_FLAG_INIT;
f.clear();
const std::atomic_flag& cf = f;
EXPECT_EQ(fmt::format("{}", cf), "false");
}
#endif // __cpp_lib_atomic_flag_test
@ -402,5 +464,31 @@ TEST(std_test, format_shared_ptr) {
TEST(std_test, format_reference_wrapper) {
int num = 35;
EXPECT_EQ("35", fmt::to_string(std::cref(num)));
EXPECT_EQ(fmt::to_string(std::cref(num)), "35");
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
}
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
struct type_with_format_as {};
int format_as(type_with_format_as) { return 20; }
TEST(std_test, format_reference_wrapper_with_format_as) {
type_with_format_as t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
}
struct type_with_format_as_string {};
std::string format_as(type_with_format_as_string) { return "foo"; }
TEST(std_test, format_reference_wrapper_with_format_as_string) {
type_with_format_as_string t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
}

View File

@ -31,17 +31,6 @@ extern const char* const file_content;
// Opens a buffered file for reading.
auto open_buffered_file(FILE** fp = nullptr) -> fmt::buffered_file;
inline auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
template <typename Char> class basic_test_string {
private:
std::basic_string<Char> value_;

View File

@ -74,6 +74,7 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
TEST(xchar_test, format) {
EXPECT_EQ(fmt::format(L"{}", 42), L"42");
EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2");
EXPECT_EQ(fmt::format(L"{}", 1e100), L"1e+100");
EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc");
EXPECT_EQ(fmt::format(L"{}", L'z'), L"z");
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
@ -171,6 +172,13 @@ TEST(xchar_test, join) {
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
}
#ifdef __cpp_lib_byte
TEST(xchar_test, join_bytes) {
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
EXPECT_EQ(fmt::format(L"{}", fmt::join(v, L", ")), L"1, 2, 3");
}
#endif
enum streamable_enum {};
std::wostream& operator<<(std::wostream& os, streamable_enum) {
@ -224,106 +232,9 @@ TEST(xchar_test, chrono) {
EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42)));
EXPECT_EQ(fmt::format(L"{:%F}", tm), L"2016-04-25");
EXPECT_EQ(fmt::format(L"{:%T}", tm), L"11:22:33");
}
std::wstring system_wcsftime(const std::wstring& format, const std::tm* timeptr,
std::locale* locptr = nullptr) {
auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<wchar_t>>(loc);
std::wostringstream os;
os.imbue(loc);
facet.put(os, os, L' ', timeptr, format.c_str(),
format.c_str() + format.size());
#ifdef _WIN32
// Workaround a bug in older versions of Universal CRT.
auto str = os.str();
if (str == L"-0000") str = L"+0000";
return str;
#else
return os.str();
#endif
}
TEST(chrono_test_wchar, time_point) {
auto t1 = std::chrono::time_point_cast<std::chrono::seconds>(
std::chrono::system_clock::now());
std::vector<std::wstring> spec_list = {
L"%%", L"%n", L"%t", L"%Y", L"%EY", L"%y", L"%Oy", L"%Ey", L"%C",
L"%EC", L"%G", L"%g", L"%b", L"%h", L"%B", L"%m", L"%Om", L"%U",
L"%OU", L"%W", L"%OW", L"%V", L"%OV", L"%j", L"%d", L"%Od", L"%e",
L"%Oe", L"%a", L"%A", L"%w", L"%Ow", L"%u", L"%Ou", L"%H", L"%OH",
L"%I", L"%OI", L"%M", L"%OM", L"%S", L"%OS", L"%x", L"%Ex", L"%X",
L"%EX", L"%D", L"%F", L"%R", L"%T", L"%p"};
#ifndef _WIN32
// Disabled on Windows, because these formats is not consistent among
// platforms.
spec_list.insert(spec_list.end(), {L"%c", L"%Ec", L"%r"});
#elif !FMT_HAS_C99_STRFTIME
// Only C89 conversion specifiers when using MSVCRT instead of UCRT
spec_list = {L"%%", L"%Y", L"%y", L"%b", L"%B", L"%m", L"%U",
L"%W", L"%j", L"%d", L"%a", L"%A", L"%w", L"%H",
L"%I", L"%M", L"%S", L"%x", L"%X", L"%p"};
#endif
spec_list.push_back(L"%Y-%m-%d %H:%M:%S");
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), t1));
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
}
// Timezone formatters tests makes sense for localtime.
#if FMT_HAS_C99_STRFTIME
spec_list = {L"%z", L"%Z"};
#else
spec_list = {L"%Z"};
#endif
for (const auto& spec : spec_list) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::localtime(&t);
auto sys_output = system_wcsftime(spec, &tm);
auto fmt_spec = fmt::format(L"{{:{}}}", spec);
EXPECT_EQ(sys_output, fmt::format(fmt::runtime(fmt_spec), tm));
if (spec == L"%z") {
sys_output.insert(sys_output.end() - 2, 1, L':');
EXPECT_EQ(sys_output, fmt::format(L"{:%Ez}", tm));
EXPECT_EQ(sys_output, fmt::format(L"{:%Oz}", tm));
}
}
// Separate tests for UTC, since std::time_put can use local time and ignoring
// the timezone in std::tm (if it presents on platform).
if (fmt::detail::has_member_data_tm_zone<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
std::vector<std::wstring> tz_names = {L"GMT", L"UTC"};
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", t1)));
EXPECT_THAT(tz_names, Contains(fmt::format(L"{:%Z}", tm)));
}
if (fmt::detail::has_member_data_tm_gmtoff<std::tm>::value) {
auto t = std::chrono::system_clock::to_time_t(t1);
auto tm = *std::gmtime(&t);
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", t1));
EXPECT_EQ(L"+0000", fmt::format(L"{:%z}", tm));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", t1));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Ez}", tm));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", t1));
EXPECT_EQ(L"+00:00", fmt::format(L"{:%Oz}", tm));
}
auto t = fmt::sys_time<std::chrono::seconds>(std::chrono::seconds(290088000));
EXPECT_EQ(fmt::format("{:%Y-%m-%d %H:%M:%S}", t), "1979-03-12 12:00:00");
}
TEST(xchar_test, color) {
@ -468,7 +379,7 @@ TEST(locale_test, int_formatter) {
f.parse(parse_ctx);
auto buf = fmt::memory_buffer();
fmt::basic_format_context<fmt::appender, char> format_ctx(
fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
fmt::appender(buf), {}, fmt::locale_ref(loc));
f.format(12345, format_ctx);
EXPECT_EQ(fmt::to_string(buf), "12,345");
}
@ -491,12 +402,20 @@ TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
}
TEST(std_test_xchar, format_bitset) {
auto bs = std::bitset<6>(42);
EXPECT_EQ(fmt::format(L"{}", bs), L"101010");
EXPECT_EQ(fmt::format(L"{:0>8}", bs), L"00101010");
EXPECT_EQ(fmt::format(L"{:-^12}", bs), L"---101010---");
}
TEST(std_test_xchar, complex) {
auto s = fmt::format(L"{}", std::complex<double>(1, 2));
EXPECT_EQ(s, L"(1+2i)");
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)),
L"(1.00+2.00i)");
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) ");
EXPECT_EQ(fmt::format(L"{:-<8}", std::complex<double>(1, 2)), L"(1+2i)--");
}
TEST(std_test_xchar, optional) {