From 5ff285a9ed91944b686cd1d77ff28bafa9975695 Mon Sep 17 00:00:00 2001 From: Simone <91993281+SimoneN64@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:10:49 +0000 Subject: [PATCH] Squashed 'external/fmt/' changes from 3b70966df..093b39ca5 093b39ca5 Update docs for meson (#4291) 2c3a5698e Simplify a copying the fill from basic_specs fc1b0f348 Clarify use of FMT_THROW in a comment 1d066890c Resolve C4702 unreachable code warnings dad323751 Fix a bug when copying the fill from basic_specs 880e1494d Improve xchar support for std::bitset formatter e3ddede6c Update version e9ec4fdc8 Bump version feb72126b Readd FMT_NO_UNIQUE_ADDRESS 8d517e54c Update changelog 563fc74ae Update changelog 3e04222d5 Restore ABI compatibility with 11.0.2 853df39d0 Mention compile-time formatting 11742a09c Clarify that format_string should be used instead of fstring da24fac10 Document fstring 5fa4bdd75 Define CMake components to allow docs to be installed separately (#4276) 3c8aad8df Update the release script 0e8aad961 Update version debe784aa Update changelog f6d112567 Update changelog 73d0d3f75 Fix github API call 08f60f1ef Update changelog faf3f8408 Bump version f3a41441d Replace requests with urllib 3f33cb21d Update changelog b07a90386 Update changelog a6fba5177 Update changelog 25e292998 Update changelog 00ab2e98b Update changelog a3ef285ae Always inline const_check to improve debug codegen in clang 28d1abc9d Update changelog 90704b9ef Update changelog 86dae01c2 Fix compatibility with older versions of VS (#4271) d8a79eafd Document formatting of bit-fields and fields of packed structs 7c3d0152e Use the _MSVC_STL_UPDATE macro to detect STL (#4267) 7c50da538 Allow getting size of dynamic format arg store (#4270) 873670ba3 Make parameter basic_memory_buffer& buf of to_string const 735d4cc05 Update changelog 141380172 Allow disabling by define FMT_CPP_LIB_FILESYSTEM=0 (#4259) 4302d7429 Update changelog 0f51ea79d Update changelog 9600fee02 Include only if FMT_CPP_LIB_FILESYSTEM is set (#4258) 47a66c5ec Bump msys2/setup-msys2 from 2.24.0 to 2.25.0 (#4250) 385c01dc7 Allow bit_cast to work for 80bit long double (#4246) df249d8ad Remove an old workaround dfad80d1c Remove an old workaround 536cabd56 Export all range join overloads (#4239) b1a054706 Remove more MSVC 2015 workarounds and fix string_view checks bfd95392c Remove MSVC 2015 workaround 9ced61bca Replace std::forward for clang-tidy (#4236) 75e5be6ad Sort specifiers a169d7fa4 Fix chrono formatting syntax doc (#4235) a6c45dfea Fix modular build a35389b3c Corrently handle buffer flush 5a3576acc Implement fmt::join for tuple-like objects (#4230) 542600013 Suppress MSVC warnings "C4127: conditional expression is constant" by used const_check (#4233) 720da57ba Remove reference to unused intrinsic 680db66c3 Explicitly export symbols from detail 56ce41ef6 Remove initializer_list dependency cf50e4d6a Fix const[expr] in context API 6580d7b80 Cleanup the format API 7e73566ce Minor cleanup 8523dba2d Make constexpr precede explicit consistently e3d3b24fc Minor cleanup 1521bba70 Use consistent types for argument count 00649552a Bump github/codeql-action from 3.26.6 to 3.27.0 (#4223) 4b8e2838f More cleanup 7d4662f7a Remove FMT_BUILTIN_CTZ 27110bc47 Minor cleanup 68f315376 Fix narrowing conversion warning in struct fstring (#4210) 168df9a06 Implement fmt::format_to into std::vector (#4211) 4daa3d591 Fix error: cannot use 'try' with exceptions disabled in Win LLVM Clang (#4208) e9eaa27e5 Add std::exception to the docs 2b6a786e3 Use standard context in print a16ff5787 Add support for code units > 0xFFFF in fill 601be1cbe Add support for code units > 0xFFFF in fill 58c185b63 Changing type of data_ to size_t to avoid compilation warnings (#4200) a0a9ba2af Fix hashes cc2ba8f9e Cleanup cifuzz action a18d42b20 Simplify lint (#4197) 4046f9727 Fix -Wmissing-noreturn warning (#4194) 6bdc12a19 detail_exported -> detail 786a4b096 Cleanup fixed_string 2cb3b7c64 Update README.md e9cba6905 Update README.md 02537548f Cleanup an example c68c5fa7c Test FMT_BUILTIN_TYPES 22701d5f6 Address build failures when using Tip-of-Tree clang. (#4187) e62c41ffb Conform `std::iterator_traits` to [iterator.traits]/1 (#4185) 18792893d Silencing Wextra-semi warning (#4188) c90bc9186 Bump actions/checkout from 4.1.6 to 4.2.0 (#4182) c95722ad6 Improve naming consistency db06b0df8 Use countl_zero in bigint b9ec48d9c Cleanup bigint 3faf6f181 Add min_of/max_of d64b100a3 Relax constexpr ff9ee0461 Fix handling FMT_BUILTIN_TYPES 1c5883bef Test nondeterministic conversion to format string cacc3108c Don't assume repeated evaluation of string literal produce the same pointer fade652ad Require clang >=15 for _BitInt support (#4176) 96dca569a Module linkage fixes for shared build (#4169) 891c9a73a Cleanup format API 9282222b7 Export more e5b20ff0d Deprecate detail::locale_ref ff9222354 Simplify locale handling 80c4d42c6 Cleanup format.h git-subtree-dir: external/fmt git-subtree-split: 093b39ca5eea129b111060839602bcfaf295125a --- .github/workflows/cifuzz.yml | 6 +- .github/workflows/doc.yml | 2 +- .github/workflows/lint.yml | 12 +- .github/workflows/linux.yml | 2 +- .github/workflows/macos.yml | 2 +- .github/workflows/scorecard.yml | 4 +- .github/workflows/windows.yml | 6 +- CMakeLists.txt | 19 +- ChangeLog.md | 238 +++++++++- README.md | 1 + doc/api.md | 75 ++-- doc/get-started.md | 2 +- doc/index.md | 4 +- doc/syntax.md | 6 +- include/fmt/args.h | 3 + include/fmt/base.h | 180 +++++--- include/fmt/chrono.h | 40 +- include/fmt/compile.h | 13 +- include/fmt/format-inl.h | 16 +- include/fmt/format.h | 715 +++++++++++++----------------- include/fmt/os.h | 28 +- include/fmt/ostream.h | 12 +- include/fmt/printf.h | 6 +- include/fmt/ranges.h | 120 +++-- include/fmt/std.h | 16 +- include/fmt/xchar.h | 35 +- src/format.cc | 1 + src/os.cc | 2 +- support/{manage.py => release.py} | 90 ++-- test/CMakeLists.txt | 3 +- test/args-test.cc | 14 + test/base-test.cc | 43 +- test/compile-test.cc | 23 +- test/format-impl-test.cc | 4 +- test/format-test.cc | 25 +- test/no-builtin-types-test.cc | 24 + test/ranges-test.cc | 14 +- test/scan.h | 4 +- test/std-test.cc | 3 + test/test-assert.h | 2 +- test/xchar-test.cc | 27 +- 41 files changed, 1068 insertions(+), 774 deletions(-) rename support/{manage.py => release.py} (69%) create mode 100644 test/no-builtin-types-test.cc diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 88144dd9..50182ee9 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -10,18 +10,20 @@ jobs: steps: - name: Build fuzzers id: build - uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a with: oss-fuzz-project-name: 'fmt' dry-run: false language: c++ + - name: Run fuzzers - uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a with: oss-fuzz-project-name: 'fmt' fuzz-seconds: 300 dry-run: false language: c++ + - name: Upload crash uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 if: failure() && steps.build.outcome == 'success' diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 3098d458..019a85f1 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Add Ubuntu mirrors run: | diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 51a62f46..cbef56a4 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,14 +13,16 @@ jobs: format_code: runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Install clang-format - uses: aminya/setup-cpp@290824452986e378826155f3379d31bce8753d76 # v0.37.0 - with: - clangformat: 17.0.5 + run: | + wget https://apt.llvm.org/llvm.sh + sudo bash ./llvm.sh 17 + sudo apt install clang-format-17 - name: Run clang-format run: | - find include src -name '*.h' -o -name '*.cc' | xargs clang-format -i -style=file -fallback-style=none + find include src -name '*.h' -o -name '*.cc' | \ + xargs clang-format-17 -i -style=file -fallback-style=none git diff --exit-code diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index b57814b4..97150467 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -62,7 +62,7 @@ jobs: shared: -DBUILD_SHARED_LIBS=ON steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set timezone run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 5713c3e8..3543ef57 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -25,7 +25,7 @@ jobs: runs-on: '${{ matrix.os }}' steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set timezone run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 9d38f77e..b363a6f8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -29,7 +29,7 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 with: persist-credentials: false @@ -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@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 + uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 with: sarif_file: results.sarif diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 79ca1814..5403e652 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -36,7 +36,7 @@ jobs: standard: 20 steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set timezone run: tzutil /s "Ekaterinburg Standard Time" @@ -78,12 +78,12 @@ jobs: - name: Set timezone run: tzutil /s "Ekaterinburg Standard Time" shell: cmd - - uses: msys2/setup-msys2@5df0ca6cbf14efcd08f8d5bd5e049a3cc8e07fd2 # v2.24.0 + - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 with: release: false msystem: ${{matrix.sys}} pacboy: cc:p cmake:p ninja:p lld:p - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Configure run: cmake -B ../build -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug env: { LDFLAGS: -fuse-ld=lld } diff --git a/CMakeLists.txt b/CMakeLists.txt index 1606e886..586ead51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -426,7 +426,9 @@ if (FMT_INSTALL) endif() # Install the library and headers. - install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} + install(TARGETS ${INSTALL_TARGETS} + COMPONENT core + EXPORT ${targets_export_name} LIBRARY DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR} PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" @@ -439,13 +441,15 @@ if (FMT_INSTALL) FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) # Install version, config and target files. - install( - FILES ${project_config} ${version_config} - DESTINATION ${FMT_CMAKE_DIR}) + install(FILES ${project_config} ${version_config} + DESTINATION ${FMT_CMAKE_DIR} + COMPONENT core) install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} - NAMESPACE fmt::) + NAMESPACE fmt:: + COMPONENT core) - install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") + install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}" + COMPONENT core) endif () function(add_doc_target) @@ -481,7 +485,8 @@ function(add_doc_target) include(GNUInstallDirs) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/ - DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt OPTIONAL) + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt + COMPONENT doc OPTIONAL) endfunction() if (FMT_DOC) diff --git a/ChangeLog.md b/ChangeLog.md index 0eb9ba8e..09ebaed6 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,6 +1,237 @@ -# 11.1.0 - TBD +# 11.1.1 - 2024-12-27 -- Improved debug codegen. +- Fixed ABI compatibility with earlier 11.x versions + (https://github.com/fmtlib/fmt/issues/4278). + +- Defined CMake components (`core` and `doc`) to allow docs to be installed + separately (https://github.com/fmtlib/fmt/pull/4276). + Thanks @carlsmedstad. + +# 11.1.0 - 2024-12-25 + +- Improved C++20 module support + (https://github.com/fmtlib/fmt/issues/4081, + https://github.com/fmtlib/fmt/pull/4083, + https://github.com/fmtlib/fmt/pull/4084, + https://github.com/fmtlib/fmt/pull/4152, + https://github.com/fmtlib/fmt/issues/4153, + https://github.com/fmtlib/fmt/pull/4169, + https://github.com/fmtlib/fmt/issues/4190, + https://github.com/fmtlib/fmt/issues/4234, + https://github.com/fmtlib/fmt/pull/4239). + Thanks @kamrann and @Arghnews. + +- Reduced debug (unoptimized) binary code size and the number of template + instantiations when passing formatting arguments. For example, unoptimized + binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and + ~60% on clang (x86-64). + + GCC: + - Before: 161 instructions of which 105 are in reusable functions + ([godbolt](https://www.godbolt.org/z/s9bGoo4ze)). + - After: 116 instructions of which 60 are in reusable functions + ([godbolt](https://www.godbolt.org/z/r7GGGxMs6)). + + Clang: + - Before: 310 instructions of which 251 are in reusable functions + ([godbolt](https://www.godbolt.org/z/Ts88b7M9o)). + - After: 194 instructions of which 135 are in reusable functions + ([godbolt](https://www.godbolt.org/z/vcrjP8ceW)). + +- Added an experimental `fmt::writer` API that can be used for writing to + different destinations such as files or strings + (https://github.com/fmtlib/fmt/issues/2354). + For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)): + + ```c++ + #include + + void write_text(fmt::writer w) { + w.print("The answer is {}.", 42); + } + + int main() { + // Write to FILE. + write_text(stdout); + + // Write to fmt::ostream. + auto f = fmt::output_file("myfile"); + write_text(f); + + // Write to std::string. + auto sb = fmt::string_buffer(); + write_text(sb); + std::string s = sb.str(); + } + ``` + +- Added width and alignment support to the formatter of `std::error_code`. + +- Made `std::expected` formattable + (https://github.com/fmtlib/fmt/issues/4145, + https://github.com/fmtlib/fmt/pull/4148). + For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)): + + ```c++ + fmt::print("{}", std::expected()); + ``` + + prints + + ``` + expected() + ``` + + Thanks @phprus. + +- Made `fmt::is_formattable` SFINAE-friendly + (https://github.com/fmtlib/fmt/issues/4147). + +- Added support for `_BitInt` formatting when using clang + (https://github.com/fmtlib/fmt/issues/4007, + https://github.com/fmtlib/fmt/pull/4072, + https://github.com/fmtlib/fmt/issues/4140, + https://github.com/fmtlib/fmt/issues/4173, + https://github.com/fmtlib/fmt/pull/4176). + For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)): + + ```c++ + using int42 = _BitInt(42); + fmt::print("{}", int42(100)); + ``` + + Thanks @Arghnews. + +- Added the `n` specifier for tuples and pairs + (https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc. + +- Added support for tuple-like types to `fmt::join` + (https://github.com/fmtlib/fmt/issues/4226, + https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus. + +- Made more types formattable at compile time + (https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH. + +- Implemented a more efficient compile-time `fmt::formatted_size` + (https://github.com/fmtlib/fmt/issues/4102, + https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus. + +- Fixed compile-time formatting of some string types + (https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd. + +- Made compiled version of `fmt::format_to` work with + `std::back_insert_iterator>` + (https://github.com/fmtlib/fmt/issues/4206, + https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus. + +- Added a formatter for `std::reference_wrapper` + (https://github.com/fmtlib/fmt/pull/4163, + https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus. + +- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j` + and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH. + +- Made microseconds formatted as `us` instead of `µs` if the Unicode support is + disabled (https://github.com/fmtlib/fmt/issues/4088). + +- Fixed an unreleased regression in transcoding of surrogate pairs + (https://github.com/fmtlib/fmt/issues/4094, + https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus. + +- Made `fmt::appender` satisfy `std::output_iterator` concept + (https://github.com/fmtlib/fmt/issues/4092, + https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus. + +- Made `std::iterator_traits` standard-conforming + (https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter. + +- Made it easier to reuse `fmt::formatter` for types with + an implicit conversion to `std::string_view` + (https://github.com/fmtlib/fmt/issues/4036, + https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews. + +- Made it possible to disable `` use via `FMT_CPP_LIB_FILESYSTEM` + for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK + (https://github.com/fmtlib/fmt/issues/4257, + https://github.com/fmtlib/fmt/pull/4258, + https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus. + +- Fixed compatibility with platforms that use 80-bit `long double` + (https://github.com/fmtlib/fmt/issues/4245, + https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma. + +- Added support for UTF-32 code units greater than `0xFFFF` in fill + (https://github.com/fmtlib/fmt/issues/4201). + +- Fixed handling of legacy encodings on Windows with GCC + (https://github.com/fmtlib/fmt/issues/4162). + +- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference + (https://github.com/fmtlib/fmt/issues/4261, + https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel. + +- Added `fmt::dynamic_format_arg_store::size` + (https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch. + +- Removed the ability to control locale usage via an undocumented + `FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`. + +- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other + similar macros. + +- Improved include directory ordering to reduce the chance of including + incorrect headers when using multiple versions of {fmt} + (https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan. + +- Made it possible to compile a subset of {fmt} without the C++ runtime. + +- Improved documentation and README + (https://github.com/fmtlib/fmt/pull/4066, + https://github.com/fmtlib/fmt/issues/4117, + https://github.com/fmtlib/fmt/issues/4203, + https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh. + +- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110, + https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado. + +- Improved CI (https://github.com/fmtlib/fmt/pull/4155, + https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus. + +- Fixed various warnings and compilation issues + (https://github.com/fmtlib/fmt/issues/2708, + https://github.com/fmtlib/fmt/issues/4091, + https://github.com/fmtlib/fmt/issues/4109, + https://github.com/fmtlib/fmt/issues/4113, + https://github.com/fmtlib/fmt/issues/4125, + https://github.com/fmtlib/fmt/issues/4129, + https://github.com/fmtlib/fmt/pull/4130, + https://github.com/fmtlib/fmt/pull/4131, + https://github.com/fmtlib/fmt/pull/4132, + https://github.com/fmtlib/fmt/issues/4133, + https://github.com/fmtlib/fmt/issues/4144, + https://github.com/fmtlib/fmt/issues/4150, + https://github.com/fmtlib/fmt/issues/4158, + https://github.com/fmtlib/fmt/pull/4159, + https://github.com/fmtlib/fmt/issues/4160, + https://github.com/fmtlib/fmt/pull/4170, + https://github.com/fmtlib/fmt/issues/4177, + https://github.com/fmtlib/fmt/pull/4187, + https://github.com/fmtlib/fmt/pull/4188, + https://github.com/fmtlib/fmt/pull/4194, + https://github.com/fmtlib/fmt/pull/4200, + https://github.com/fmtlib/fmt/issues/4205, + https://github.com/fmtlib/fmt/issues/4207, + https://github.com/fmtlib/fmt/pull/4208, + https://github.com/fmtlib/fmt/pull/4210, + https://github.com/fmtlib/fmt/issues/4220, + https://github.com/fmtlib/fmt/issues/4231, + https://github.com/fmtlib/fmt/issues/4232, + https://github.com/fmtlib/fmt/pull/4233, + https://github.com/fmtlib/fmt/pull/4236, + https://github.com/fmtlib/fmt/pull/4267, + https://github.com/fmtlib/fmt/pull/4271). + Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega, + @c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao. # 11.0.2 - 2024-07-20 @@ -272,6 +503,9 @@ - Fixed handling of negative ids in `fmt::basic_format_args::get` (https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota. +- Fixed handling of a buffer boundary on flush + (https://github.com/fmtlib/fmt/issues/4229). + - Improved named argument validation (https://github.com/fmtlib/fmt/issues/3817). diff --git a/README.md b/README.md index 5f9249d4..fd845db2 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,7 @@ converts to `std::print`.) - [ccache](https://ccache.dev/): a compiler cache - [ClickHouse](https://github.com/ClickHouse/ClickHouse): an analytical database management system +- [ContextVision](https://www.contextvision.com/): medical imaging software - [Contour](https://github.com/contour-terminal/contour/): a modern terminal emulator - [CUAUV](https://cuauv.org/): Cornell University\'s autonomous diff --git a/doc/api.md b/doc/api.md index bf0df731..e86f0b06 100644 --- a/doc/api.md +++ b/doc/api.md @@ -269,18 +269,16 @@ that support C++20 `consteval`. On older compilers you can use the Unused arguments are allowed as in Python's `str.format` and ordinary functions. -::: basic_format_string +See [Type Erasure](#type-erasure) for an example of how to enable compile-time +checks in your own functions with `fmt::format_string` while avoiding template +bloat. + +::: fstring ::: format_string ::: runtime(string_view) -### Named Arguments - -::: arg(const Char*, const T&) - -Named arguments are not supported in compile-time checks at the moment. - ### Type Erasure You can create your own formatting function with compile-time checks and @@ -317,6 +315,12 @@ parameterized version. ::: basic_format_arg +### Named Arguments + +::: arg(const Char*, const T&) + +Named arguments are not supported in compile-time checks at the moment. + ### Compatibility ::: basic_string_view @@ -375,18 +379,17 @@ allocator: using custom_string = std::basic_string, custom_allocator>; - custom_string vformat(custom_allocator alloc, fmt::string_view format_str, - fmt::format_args args) { + auto vformat(custom_allocator alloc, fmt::string_view fmt, + fmt::format_args args) -> custom_string { auto buf = custom_memory_buffer(alloc); - fmt::vformat_to(std::back_inserter(buf), format_str, args); + fmt::vformat_to(std::back_inserter(buf), fmt, args); return custom_string(buf.data(), buf.size(), alloc); } template - inline custom_string format(custom_allocator alloc, - fmt::string_view format_str, - const Args& ... args) { - return vformat(alloc, format_str, fmt::make_format_args(args...)); + auto format(custom_allocator alloc, fmt::string_view fmt, + const Args& ... args) -> custom_string { + return vformat(alloc, fmt, fmt::make_format_args(args...)); } The allocator will be used for the output container only. Formatting @@ -410,11 +413,11 @@ locale: that take `std::locale` as a parameter. The locale type is a template parameter to avoid the expensive `` include. -::: format(const Locale&, format_string, T&&...) +::: format(detail::locale_ref, format_string, T&&...) -::: format_to(OutputIt, const Locale&, format_string, T&&...) +::: format_to(OutputIt, detail::locale_ref, format_string, T&&...) -::: formatted_size(const Locale&, format_string, T&&...) +::: formatted_size(detail::locale_ref, format_string, T&&...) ### Legacy Compile-Time Checks @@ -498,10 +501,13 @@ chrono-format-specifications). - [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) - [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) - [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code) +- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception) - [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path) -- [`std::monostate`](https://en.cppreference.com/w/cpp/utility/variant/monostate) +- [`std::monostate`]( + https://en.cppreference.com/w/cpp/utility/variant/monostate) - [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) -- [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location) +- [`std::source_location`]( + https://en.cppreference.com/w/cpp/utility/source_location) - [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id) - [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant) @@ -509,7 +515,7 @@ chrono-format-specifications). ::: ptr(const std::shared_ptr&) -### Formatting Variants +### Variants A `std::variant` is only formattable if every variant alternative is formattable, and requires the `__cpp_lib_variant` [library @@ -525,15 +531,32 @@ feature](https://en.cppreference.com/w/cpp/feature_test). fmt::print("{}", std::variant()); // Output: variant(monostate) +## Bit-Fields and Packed Structs + +To format a bit-field or a field of a struct with `__attribute__((packed))` +applied to it, you need to convert it to the underlying or compatible type via +a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)): + +```c++ +struct smol { + int bit : 1; +}; + +auto s = smol(); +fmt::print("{}", +s.bit); +``` + +This is a known limitation of "perfect" forwarding in C++. + ## Format String Compilation -`fmt/compile.h` provides format string compilation 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 +`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 the format context type as a template parameter in their `formatter` specializations. For example: diff --git a/doc/get-started.md b/doc/get-started.md index e61da882..466d1c1b 100644 --- a/doc/get-started.md +++ b/doc/get-started.md @@ -202,7 +202,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 diff --git a/doc/index.md b/doc/index.md index b170f9f2..4f28e114 100644 --- a/doc/index.md +++ b/doc/index.md @@ -122,8 +122,8 @@ hide:

The library is highly portable and requires only a minimal subset of - C++11 features which are available in GCC 4.9, Clang 3.4, MSVC 19.0 - (2015) and later. Newer compiler and standard library features are used + C++11 features which are available in GCC 4.9, Clang 3.4, MSVC 19.10 + (2017) and later. Newer compiler and standard library features are used if available, and enable additional functionality.

diff --git a/doc/syntax.md b/doc/syntax.md index 1a44e867..46d7d2fd 100644 --- a/doc/syntax.md +++ b/doc/syntax.md @@ -706,12 +706,12 @@ The available padding modifiers (*padding_modifier*) are: | Type | Meaning | |-------|-----------------------------------------| -| `'-'` | Pad a numeric result with spaces. | -| `'_'` | Do not pad a numeric result string. | +| `'_'` | Pad a numeric result with spaces. | +| `'-'` | Do not pad a numeric result string. | | `'0'` | Pad a numeric result string with zeros. | These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`, -`'V'`, `'W'`, `'m'`, `'j'`, `'Y'` presentation types. +`'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types. ## Range Format Specifications diff --git a/include/fmt/args.h b/include/fmt/args.h index 6ed30c0b..3ff47880 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -210,6 +210,9 @@ template class dynamic_format_arg_store { data_.reserve(new_cap); named_info_.reserve(new_cap_named); } + + /// Returns the number of elements in the store. + size_t size() const noexcept { return data_.size(); } }; FMT_END_NAMESPACE diff --git a/include/fmt/base.h b/include/fmt/base.h index b36faabc..a6948d40 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -21,7 +21,7 @@ #endif // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 110002 +#define FMT_VERSION 110101 // Detect compiler versions. #if defined(__clang__) && !defined(__ibmxl__) @@ -146,6 +146,8 @@ // Use the provided definition. #elif defined(__GNUC__) && !defined(__EXCEPTIONS) # define FMT_USE_EXCEPTIONS 0 +#elif defined(__clang__) && !defined(__cpp_exceptions) +# define FMT_USE_EXCEPTIONS 0 #elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS # define FMT_USE_EXCEPTIONS 0 #else @@ -159,6 +161,20 @@ # define FMT_CATCH(x) if (false) #endif +#ifdef FMT_NO_UNIQUE_ADDRESS +// Use the provided definition. +#elif FMT_CPLUSPLUS < 202002L +// Not supported. +#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) +# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] +// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). +#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION +# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#endif +#ifndef FMT_NO_UNIQUE_ADDRESS +# define FMT_NO_UNIQUE_ADDRESS +#endif + #if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) # define FMT_FALLTHROUGH [[fallthrough]] #elif defined(__clang__) @@ -332,6 +348,13 @@ struct monostate { # define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 #endif +template constexpr auto min_of(T a, T b) -> T { + return a < b ? a : b; +} +template constexpr auto max_of(T a, T b) -> T { + return a > b ? a : b; +} + namespace detail { // Suppresses "unused variable" warnings with the method described in // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. @@ -355,7 +378,9 @@ constexpr auto is_constant_evaluated(bool default_value = false) noexcept } // Suppresses "conditional expression is constant" warnings. -template constexpr auto const_check(T value) -> T { return value; } +template FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T { + return val; +} FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); @@ -394,7 +419,7 @@ inline auto map(uint128_opt) -> monostate { return {}; } #endif #ifndef FMT_USE_BITINT -# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1400) +# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500) #endif #if FMT_USE_BITINT @@ -562,8 +587,8 @@ template class basic_string_view { // Lexicographically compare this string reference to other. FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = detail::compare(data_, other.data_, str_size); + int result = + detail::compare(data_, other.data_, min_of(size_, other.size_)); if (result != 0) return result; return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); } @@ -714,7 +739,7 @@ class basic_specs { max_fill_size = 4 }; - unsigned long data_ = 1 << fill_size_shift; + size_t data_ = 1 << fill_size_shift; // Character (code unit) type is erased to prevent template bloat. char fill_data_[max_fill_size] = {' '}; @@ -793,7 +818,8 @@ class basic_specs { template constexpr auto fill_unit() const -> Char { using uchar = unsigned char; return static_cast(static_cast(fill_data_[0]) | - (static_cast(fill_data_[1]) << 8)); + (static_cast(fill_data_[1]) << 8) | + (static_cast(fill_data_[2]) << 16)); } FMT_CONSTEXPR void set_fill(char c) { @@ -809,12 +835,19 @@ class basic_specs { unsigned uchar = static_cast>(s[0]); fill_data_[0] = static_cast(uchar); fill_data_[1] = static_cast(uchar >> 8); + fill_data_[2] = static_cast(uchar >> 16); return; } FMT_ASSERT(size <= max_fill_size, "invalid fill"); for (size_t i = 0; i < size; ++i) fill_data_[i & 3] = static_cast(s[i]); } + + FMT_CONSTEXPR void set_fill(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. @@ -842,7 +875,7 @@ template class parse_context { using char_type = Char; using iterator = const Char*; - explicit constexpr parse_context(basic_string_view fmt, + constexpr explicit parse_context(basic_string_view fmt, int next_arg_id = 0) : fmt_(fmt), next_arg_id_(next_arg_id) {} @@ -1012,15 +1045,15 @@ template struct named_arg : view { static_assert(!is_named_arg::value, "nested named arguments"); }; -template constexpr auto count() -> size_t { return B ? 1 : 0; } -template constexpr auto count() -> size_t { +template constexpr auto count() -> int { return B ? 1 : 0; } +template constexpr auto count() -> int { return (B1 ? 1 : 0) + count(); } -template constexpr auto count_named_args() -> size_t { +template constexpr auto count_named_args() -> int { return count::value...>(); } -template constexpr auto count_static_named_args() -> size_t { +template constexpr auto count_static_named_args() -> int { return count::value...>(); } @@ -1180,7 +1213,7 @@ class compile_parse_context : public parse_context { using base = parse_context; public: - explicit FMT_CONSTEXPR compile_parse_context(basic_string_view fmt, + FMT_CONSTEXPR explicit compile_parse_context(basic_string_view fmt, int num_args, const type* types, int next_arg_id = 0) : base(fmt, next_arg_id), num_args_(num_args), types_(types) {} @@ -1627,16 +1660,16 @@ template struct arg_pack {}; template class format_string_checker { private: - type types_[NUM_ARGS > 0 ? NUM_ARGS : 1]; - named_arg_info named_args_[NUM_NAMED_ARGS > 0 ? NUM_NAMED_ARGS : 1]; + type types_[max_of(1, NUM_ARGS)]; + named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; compile_parse_context context_; using parse_func = auto (*)(parse_context&) -> const Char*; - parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; + parse_func parse_funcs_[max_of(1, NUM_ARGS)]; public: template - explicit FMT_CONSTEXPR format_string_checker(basic_string_view fmt, + FMT_CONSTEXPR explicit format_string_checker(basic_string_view fmt, arg_pack) : types_{mapped_type_constant::value...}, named_args_{}, @@ -1694,7 +1727,7 @@ template class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. FMT_MSC_WARNING(suppress : 26495) - FMT_CONSTEXPR20 buffer(grow_fun grow, size_t sz) noexcept + FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept : size_(sz), capacity_(sz), grow_(grow) {} constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, @@ -1740,7 +1773,7 @@ template class buffer { // the new elements may not be initialized. FMT_CONSTEXPR void try_resize(size_t count) { try_reserve(count); - size_ = count <= capacity_ ? count : capacity_; + size_ = min_of(count, capacity_); } // Tries increasing the buffer capacity to `new_capacity`. It can increase the @@ -1788,9 +1821,9 @@ template class buffer { }; struct buffer_traits { - explicit buffer_traits(size_t) {} - auto count() const -> size_t { return 0; } - auto limit(size_t size) -> size_t { return size; } + constexpr explicit buffer_traits(size_t) {} + constexpr auto count() const -> size_t { return 0; } + constexpr auto limit(size_t size) const -> size_t { return size; } }; class fixed_buffer_traits { @@ -1799,12 +1832,12 @@ class fixed_buffer_traits { size_t limit_; public: - explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} - auto count() const -> size_t { return count_; } - auto limit(size_t size) -> size_t { + constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} + constexpr auto count() const -> size_t { return count_; } + FMT_CONSTEXPR auto limit(size_t size) -> size_t { size_t n = limit_ > count_ ? limit_ - count_ : 0; count_ += size; - return size < n ? size : n; + return min_of(size, n); } }; @@ -1962,15 +1995,37 @@ template class counting_buffer : public buffer { template struct is_back_insert_iterator> : std::true_type {}; +template +struct has_back_insert_iterator_container_append : std::false_type {}; +template +struct has_back_insert_iterator_container_append< + OutputIt, InputIt, + void_t()) + .append(std::declval(), + std::declval()))>> : std::true_type {}; + // An optimized version of std::copy with the output value type (T). template ::value)> + FMT_ENABLE_IF(is_back_insert_iterator::value&& + has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { get_container(out).append(begin, end); return out; } +template ::value && + !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value)> +FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + auto& c = get_container(out); + c.insert(c.end(), begin, end); + return out; +} + template ::value)> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { @@ -2146,7 +2201,8 @@ template class value { template ::value)> value(const T& named_arg) : value(named_arg.value) {} - template ::value)> + template ::value || !FMT_BUILTIN_TYPES)> FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} FMT_ALWAYS_INLINE value(const named_arg_info* args, size_t size) @@ -2220,9 +2276,12 @@ struct locale_ref { public: constexpr locale_ref() : locale_(nullptr) {} - template explicit locale_ref(const Locale& loc); - explicit operator bool() const noexcept { return locale_ != nullptr; } -#endif + + template + locale_ref(const Locale& loc); + + inline explicit operator bool() const noexcept { return locale_ != nullptr; } +#endif // FMT_USE_LOCALE template auto get() const -> Locale; }; @@ -2243,16 +2302,15 @@ constexpr auto make_descriptor() -> unsigned long long { : is_unpacked_bit | NUM_ARGS; } -template +template using arg_t = conditional_t, basic_format_arg>; -template struct named_arg_store { // args_[0].named_args points to named_args to avoid bloating format_args. - // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. - arg_t args[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)]; + arg_t args[1 + NUM_ARGS]; named_arg_info named_args[NUM_NAMED_ARGS]; template @@ -2280,13 +2338,13 @@ struct named_arg_store { // An array of references to arguments. It can be implicitly converted to // `basic_format_args` for passing into type-erased formatting functions // such as `vformat`. It is a plain struct to reduce binary size in debug mode. -template struct format_arg_store { // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. using type = conditional_t[NUM_ARGS != 0 ? NUM_ARGS : +1], + arg_t[max_of(1, NUM_ARGS)], named_arg_store>; type args; }; @@ -2372,11 +2430,6 @@ template class basic_appender { detail::buffer* container; public: - using iterator_category = int; - using value_type = T; - using pointer = T*; - using reference = T&; - using difference_type = decltype(pointer() - pointer()); using container_type = detail::buffer; FMT_CONSTEXPR basic_appender(detail::buffer& buf) : container(&buf) {} @@ -2503,7 +2556,7 @@ template class basic_format_args { return static_cast((desc_ >> shift) & mask); } - template + template using store = detail::format_arg_store; @@ -2513,14 +2566,14 @@ template class basic_format_args { constexpr basic_format_args() : desc_(0), args_(nullptr) {} /// Constructs a `basic_format_args` object from `format_arg_store`. - template constexpr FMT_ALWAYS_INLINE basic_format_args( const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), values_(s.args) {} - template detail::max_packed_args)> constexpr basic_format_args(const store& s) : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), @@ -2571,10 +2624,11 @@ template class basic_format_args { }; // A formatting context. -class context : private detail::locale_ref { +class context { private: appender out_; format_args args_; + FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_; public: /// The character type for the output. @@ -2590,24 +2644,26 @@ class context : private detail::locale_ref { /// in the object so make sure they have appropriate lifetimes. FMT_CONSTEXPR context(iterator out, format_args args, detail::locale_ref loc = {}) - : locale_ref(loc), out_(out), args_(args) {} + : out_(out), args_(args), loc_(loc) {} context(context&&) = default; context(const context&) = delete; void operator=(const context&) = delete; FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } - auto arg(string_view name) -> format_arg { return args_.get(name); } - FMT_CONSTEXPR auto arg_id(string_view name) -> int { + inline auto arg(string_view name) const -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(string_view name) const -> int { return args_.get_id(name); } // Returns an iterator to the beginning of the output range. - FMT_CONSTEXPR auto out() -> iterator { return out_; } + FMT_CONSTEXPR auto out() const -> iterator { return out_; } // Advances the begin iterator to `it`. - void advance_to(iterator) {} + FMT_CONSTEXPR void advance_to(iterator) {} - FMT_CONSTEXPR auto locale() -> detail::locale_ref { return *this; } + FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; } }; template struct runtime_format_string { @@ -2624,7 +2680,8 @@ template struct runtime_format_string { */ inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } -/// A compile-time format string. +/// A compile-time format string. Use `format_string` in the public API to +/// prevent type deduction. template struct fstring { private: static constexpr int num_static_named_args = @@ -2657,8 +2714,9 @@ template struct fstring { template ::value)> FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { + auto sv = string_view(str); if (FMT_USE_CONSTEVAL) - detail::parse_format_string(s, checker(s, arg_pack())); + detail::parse_format_string(sv, checker(sv, arg_pack())); #ifdef FMT_ENFORCE_COMPILE_STRING static_assert( FMT_USE_CONSTEVAL && sizeof(s) != 0, @@ -2711,8 +2769,8 @@ struct formatter(), + int NUM_ARGS = sizeof...(T), + int NUM_NAMED_ARGS = detail::count_named_args(), unsigned long long DESC = detail::make_descriptor()> constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) -> detail::format_arg_store { @@ -2851,7 +2909,7 @@ FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args); template FMT_INLINE void print(format_string fmt, T&&... args) { vargs va = {{args...}}; - if (!detail::use_utf8) + if (detail::const_check(!detail::use_utf8)) return detail::vprint_mojibake(stdout, fmt.str, va, false); return detail::is_locking() ? vprint_buffered(stdout, fmt.str, va) : vprint(fmt.str, va); @@ -2868,7 +2926,8 @@ FMT_INLINE void print(format_string fmt, T&&... args) { template FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - if (!detail::use_utf8) return detail::vprint_mojibake(f, fmt.str, va, false); + if (detail::const_check(!detail::use_utf8)) + return detail::vprint_mojibake(f, fmt.str, va, false); return detail::is_locking() ? vprint_buffered(f, fmt.str, va) : vprint(f, fmt.str, va); } @@ -2878,8 +2937,9 @@ FMT_INLINE void print(FILE* f, format_string fmt, T&&... args) { template FMT_INLINE void println(FILE* f, format_string fmt, T&&... args) { vargs va = {{args...}}; - return detail::use_utf8 ? vprintln(f, fmt.str, va) - : detail::vprint_mojibake(f, fmt.str, va, true); + return detail::const_check(detail::use_utf8) + ? vprintln(f, fmt.str, va) + : detail::vprint_mojibake(f, fmt.str, va, true); } /// Formats `args` according to specifications in `fmt` and writes the output diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 5472eae0..abf3671e 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -444,7 +444,7 @@ struct is_same_arithmetic_type std::is_floating_point::value)> { }; -inline void throw_duration_error() { +FMT_NORETURN inline void throw_duration_error() { FMT_THROW(format_error("cannot format duration")); } @@ -540,24 +540,24 @@ inline auto localtime(std::time_t time) -> std::tm { std::time_t time_; std::tm tm_; - dispatcher(std::time_t t) : time_(t) {} + inline dispatcher(std::time_t t) : time_(t) {} - auto run() -> bool { + inline auto run() -> bool { using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } - auto handle(std::tm* tm) -> bool { return tm != nullptr; } + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } - auto handle(detail::null<>) -> bool { + inline auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } - auto fallback(int res) -> bool { return res == 0; } + inline auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION - auto fallback(detail::null<>) -> bool { + inline auto fallback(detail::null<>) -> bool { using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; @@ -591,24 +591,24 @@ inline auto gmtime(std::time_t time) -> std::tm { std::time_t time_; std::tm tm_; - dispatcher(std::time_t t) : time_(t) {} + inline dispatcher(std::time_t t) : time_(t) {} - auto run() -> bool { + inline auto run() -> bool { using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } - auto handle(std::tm* tm) -> bool { return tm != nullptr; } + inline auto handle(std::tm* tm) -> bool { return tm != nullptr; } - auto handle(detail::null<>) -> bool { + inline auto handle(detail::null<>) -> bool { using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } - auto fallback(int res) -> bool { return res == 0; } + inline auto fallback(int res) -> bool { return res == 0; } #if !FMT_MSC_VERSION - auto fallback(detail::null<>) -> bool { + inline auto fallback(detail::null<>) -> bool { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -912,7 +912,9 @@ template struct null_chrono_spec_handler { }; struct tm_format_checker : null_chrono_spec_handler { - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } + FMT_NORETURN inline void unsupported() { + FMT_THROW(format_error("no format")); + } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} @@ -1069,7 +1071,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { } } else if (precision > 0) { *out++ = '.'; - leading_zeroes = (std::min)(leading_zeroes, precision); + leading_zeroes = min_of(leading_zeroes, precision); int remaining = precision - leading_zeroes; out = detail::fill_n(out, leading_zeroes, '0'); if (remaining < num_digits) { @@ -1572,7 +1574,7 @@ class tm_writer { struct chrono_format_checker : null_chrono_spec_handler { bool has_precision_integral = false; - FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); } template FMT_CONSTEXPR void on_text(const Char*, const Char*) {} @@ -1693,14 +1695,14 @@ class get_locale { bool has_locale_ = false; public: - get_locale(bool localized, locale_ref loc) : has_locale_(localized) { + inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { if (localized) ::new (&locale_) std::locale(loc.template get()); } - ~get_locale() { + inline ~get_locale() { if (has_locale_) locale_.~locale(); } - operator const std::locale&() const { + inline operator const std::locale&() const { return has_locale_ ? locale_ : get_classic_locale(); } }; diff --git a/include/fmt/compile.h b/include/fmt/compile.h index c33427ab..68b451c7 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -42,11 +42,10 @@ struct is_compiled_string : std::is_base_of {}; #endif #if FMT_USE_NONTYPE_TEMPLATE_ARGS -template Str> +template Str> struct udl_compiled_string : compiled_string { using char_type = Char; - explicit constexpr operator basic_string_view() const { + constexpr explicit operator basic_string_view() const { return {Str.data, N - 1}; } }; @@ -525,9 +524,9 @@ FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) template ::value)> void print(std::FILE* f, const S& fmt, const Args&... args) { - memory_buffer buffer; - fmt::format_to(std::back_inserter(buffer), fmt, args...); - detail::print(f, {buffer.data(), buffer.size()}); + auto buf = memory_buffer(); + fmt::format_to(appender(buf), fmt, args...); + detail::print(f, {buf.data(), buf.size()}); } template constexpr auto operator""_cf() { +template constexpr auto operator""_cf() { using char_t = remove_cvref_t; return detail::udl_compiled_string(); diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index e4dd7ea8..14c65a09 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -26,6 +26,10 @@ # include #endif +#ifndef FMT_FUNC +# define FMT_FUNC +#endif + FMT_BEGIN_NAMESPACE namespace detail { @@ -59,8 +63,8 @@ FMT_FUNC void format_error_code(detail::buffer& out, int error_code, FMT_ASSERT(out.size() <= inline_buffer_size, ""); } -FMT_FUNC void report_error(format_func func, int error_code, - const char* message) noexcept { +FMT_FUNC void do_report_error(format_func func, int error_code, + const char* message) noexcept { memory_buffer full_message; func(full_message, error_code, message); // Don't use fwrite_all because the latter may throw. @@ -80,7 +84,7 @@ using std::locale; using std::numpunct; using std::use_facet; -template +template > locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { static_assert(std::is_same::value, ""); } @@ -130,7 +134,9 @@ FMT_FUNC auto write_loc(appender out, loc_value value, FMT_FUNC void report_error(const char* message) { #if FMT_USE_EXCEPTIONS - throw format_error(message); + // Use FMT_THROW instead of throw to avoid bogus unreachable code warnings + // from MSVC. + FMT_THROW(format_error(message)); #else fputs(message, stderr); abort(); @@ -1430,7 +1436,7 @@ FMT_FUNC void format_system_error(detail::buffer& out, int error_code, FMT_FUNC void report_system_error(int error_code, const char* message) noexcept { - report_error(format_system_error, error_code, message); + do_report_error(format_system_error, error_code, message); } FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { diff --git a/include/fmt/format.h b/include/fmt/format.h index 880d948b..c9a6054d 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -41,13 +41,12 @@ #include "base.h" #ifndef FMT_MODULE -# include // std::signbit -# include // std::byte -# include // uint32_t -# include // std::memcpy -# include // std::initializer_list -# include // std::numeric_limits -# include // std::bad_alloc +# include // std::signbit +# include // std::byte +# include // uint32_t +# include // std::memcpy +# include // std::numeric_limits +# include // std::bad_alloc # if defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI) // Workaround for pre gcc 5 libstdc++. # include // std::allocator_traits @@ -56,7 +55,7 @@ # include // std::string # include // std::system_error -// Checking FMT_CPLUSPLUS for warning suppression in MSVC. +// Check FMT_CPLUSPLUS to avoid a warning in MSVC. # if FMT_HAS_INCLUDE() && FMT_CPLUSPLUS > 201703L # include // std::bit_cast # endif @@ -69,7 +68,7 @@ # endif # if FMT_MSC_VERSION -# include // _BitScanReverse[64], _BitScanForward[64], _umul128 +# include // _BitScanReverse[64], _umul128 # endif #endif // FMT_MODULE @@ -119,11 +118,13 @@ #endif namespace std { -template <> struct iterator_traits { +template struct iterator_traits> { using iterator_category = output_iterator_tag; - using value_type = char; - using reference = char&; - using difference_type = fmt::appender::difference_type; + using value_type = T; + using difference_type = + decltype(static_cast(nullptr) - static_cast(nullptr)); + using pointer = void; + using reference = void; }; } // namespace std @@ -147,22 +148,8 @@ FMT_END_NAMESPACE # else # define FMT_THROW(x) \ ::fmt::detail::assert_fail(__FILE__, __LINE__, (x).what()) -# endif -#endif - -#ifdef FMT_NO_UNIQUE_ADDRESS -// Use the provided definition. -#elif FMT_CPLUSPLUS < 202002L -// Not supported. -#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address) -# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]] -// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485). -#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION -# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] -#endif -#ifndef FMT_NO_UNIQUE_ADDRESS -# define FMT_NO_UNIQUE_ADDRESS -#endif +# endif // FMT_USE_EXCEPTIONS +#endif // FMT_THROW // Defining FMT_REDUCE_INT_INSTANTIATIONS to 1, will reduce the number of // integer formatter template instantiations to just one by only using the @@ -172,6 +159,14 @@ FMT_END_NAMESPACE # define FMT_REDUCE_INT_INSTANTIATIONS 0 #endif +FMT_BEGIN_NAMESPACE + +template +struct is_contiguous> + : std::true_type {}; + +namespace detail { + // __builtin_clz is broken in clang with Microsoft codegen: // https://github.com/fmtlib/fmt/issues/519. #if !FMT_MSC_VERSION @@ -183,49 +178,30 @@ FMT_END_NAMESPACE # endif #endif -// __builtin_ctz is broken in Intel Compiler Classic on Windows: -// https://github.com/fmtlib/fmt/issues/2510. -#ifndef __ICL -# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION || \ - defined(__NVCOMPILER) -# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n) -# endif -# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || \ - FMT_ICC_VERSION || defined(__NVCOMPILER) -# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n) -# endif -#endif - -// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// Some compilers masquerade as both MSVC and GCC but otherwise support // __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the // MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) && \ - !defined(FMT_BUILTIN_CTZLL) -FMT_BEGIN_NAMESPACE -namespace detail { +#if FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# if !defined(__clang__) -# pragma intrinsic(_BitScanForward) +# ifndef __clang__ # pragma intrinsic(_BitScanReverse) -# if defined(_WIN64) -# pragma intrinsic(_BitScanForward64) +# ifdef _WIN64 # pragma intrinsic(_BitScanReverse64) # endif # endif inline auto clz(uint32_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; _BitScanReverse(&r, x); - FMT_ASSERT(x != 0, ""); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. - FMT_MSC_WARNING(suppress : 6102) return 31 ^ static_cast(r); } # define FMT_BUILTIN_CLZ(n) detail::clz(n) inline auto clzll(uint64_t x) -> int { + FMT_ASSERT(x != 0, ""); + FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. unsigned long r = 0; # ifdef _WIN64 _BitScanReverse64(&r, x); @@ -236,48 +212,10 @@ inline auto clzll(uint64_t x) -> int { // Scan the low 32 bits. _BitScanReverse(&r, static_cast(x)); # endif - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. return 63 ^ static_cast(r); } # define FMT_BUILTIN_CLZLL(n) detail::clzll(n) - -inline auto ctz(uint32_t x) -> int { - unsigned long r = 0; - _BitScanForward(&r, x); - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. - return static_cast(r); -} -# define FMT_BUILTIN_CTZ(n) detail::ctz(n) - -inline auto ctzll(uint64_t x) -> int { - unsigned long r = 0; - FMT_ASSERT(x != 0, ""); - FMT_MSC_WARNING(suppress : 6102) // Suppress a bogus static analysis warning. -# ifdef _WIN64 - _BitScanForward64(&r, x); -# else - // Scan the low 32 bits. - if (_BitScanForward(&r, static_cast(x))) return static_cast(r); - // Scan the high 32 bits. - _BitScanForward(&r, static_cast(x >> 32)); - r += 32; -# endif - return static_cast(r); -} -# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n) -} // namespace detail -FMT_END_NAMESPACE -#endif - -FMT_BEGIN_NAMESPACE - -template -struct is_contiguous> - : std::true_type {}; - -namespace detail { +#endif // FMT_MSC_VERSION && !defined(FMT_BUILTIN_CLZLL) FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { ignore_unused(condition); @@ -372,13 +310,14 @@ class uint128_fallback { -> uint128_fallback { return {~n.hi_, ~n.lo_}; } - friend auto operator+(const uint128_fallback& lhs, - const uint128_fallback& rhs) -> uint128_fallback { + friend FMT_CONSTEXPR auto operator+(const uint128_fallback& lhs, + const uint128_fallback& rhs) + -> uint128_fallback { auto result = uint128_fallback(lhs); result += rhs; return result; } - friend auto operator*(const uint128_fallback& lhs, uint32_t rhs) + friend FMT_CONSTEXPR auto operator*(const uint128_fallback& lhs, uint32_t rhs) -> uint128_fallback { FMT_ASSERT(lhs.hi_ == 0, ""); uint64_t hi = (lhs.lo_ >> 32) * rhs; @@ -386,7 +325,7 @@ class uint128_fallback { uint64_t new_lo = (hi << 32) + lo; return {(hi >> 32) + (new_lo < lo ? 1 : 0), new_lo}; } - friend auto operator-(const uint128_fallback& lhs, uint64_t rhs) + friend constexpr auto operator-(const uint128_fallback& lhs, uint64_t rhs) -> uint128_fallback { return {lhs.hi_ - (lhs.lo_ < rhs ? 1 : 0), lhs.lo_ - rhs}; } @@ -466,17 +405,17 @@ template <> constexpr auto num_bits() -> int { return 128; } // and 128-bit pointers to uint128_fallback. template sizeof(From))> inline auto bit_cast(const From& from) -> To { - constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned)); + constexpr auto size = static_cast(sizeof(From) / sizeof(unsigned short)); struct data_t { - unsigned value[static_cast(size)]; + unsigned short value[static_cast(size)]; } data = bit_cast(from); auto result = To(); if (const_check(is_big_endian())) { for (int i = 0; i < size; ++i) - result = (result << num_bits()) | data.value[i]; + result = (result << num_bits()) | data.value[i]; } else { for (int i = size - 1; i >= 0; --i) - result = (result << num_bits()) | data.value[i]; + result = (result << num_bits()) | data.value[i]; } return result; } @@ -552,8 +491,8 @@ constexpr auto to_pointer(OutputIt, size_t) -> T* { template FMT_CONSTEXPR20 auto to_pointer(basic_appender it, size_t n) -> T* { buffer& buf = get_container(it); + buf.try_reserve(buf.size() + n); auto size = buf.size(); - buf.try_reserve(size + n); if (buf.capacity() < size + n) return nullptr; buf.try_resize(size + n); return buf.data() + size; @@ -674,7 +613,8 @@ FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) { auto num_chars_left = to_unsigned(s.data() + s.size() - p); if (num_chars_left == 0) return; - FMT_ASSERT(num_chars_left < block_size, ""); + // Suppress bogus -Wstringop-overflow. + if (FMT_GCC_VERSION) num_chars_left &= 3; char buf[2 * block_size - 1] = {}; copy(p, p + num_chars_left, buf); const char* buf_ptr = buf; @@ -728,8 +668,7 @@ FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { template inline auto code_point_index(basic_string_view s, size_t n) -> size_t { - size_t size = s.size(); - return n < size ? n : size; + return min_of(n, s.size()); } // Calculates the index of the nth code point in a UTF-8 string. @@ -796,11 +735,6 @@ using is_double_double = bool_constant::digits == 106>; # define FMT_USE_FULL_CACHE_DRAGONBOX 0 #endif -template -struct is_locale : std::false_type {}; -template -struct is_locale> : std::true_type {}; - // An allocator that uses malloc/free to allow removing dependency on the C++ // standard libary runtime. template struct allocator { @@ -862,7 +796,7 @@ class basic_memory_buffer : public detail::buffer { if (size > new_capacity) new_capacity = size; else if (new_capacity > max_size) - new_capacity = size > max_size ? size : max_size; + new_capacity = max_of(size, max_size); T* old_data = buf.data(); T* new_data = self.alloc_.allocate(new_capacity); // Suppress a bogus -Wstringop-overflow in gcc 13.1 (#3481). @@ -880,7 +814,7 @@ class basic_memory_buffer : public detail::buffer { using value_type = T; using const_reference = const T&; - FMT_CONSTEXPR20 explicit basic_memory_buffer( + FMT_CONSTEXPR explicit basic_memory_buffer( const Allocator& alloc = Allocator()) : detail::buffer(grow), alloc_(alloc) { this->set(store_, SIZE); @@ -928,7 +862,7 @@ class basic_memory_buffer : public detail::buffer { /// Resizes the buffer to contain `count` elements. If T is a POD type new /// elements may not be initialized. - FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); } + FMT_CONSTEXPR void resize(size_t count) { this->try_resize(count); } /// Increases the buffer capacity to `new_capacity`. void reserve(size_t new_capacity) { this->try_reserve(new_capacity); } @@ -943,7 +877,7 @@ class basic_memory_buffer : public detail::buffer { using memory_buffer = basic_memory_buffer; template -FMT_NODISCARD auto to_string(basic_memory_buffer& buf) +FMT_NODISCARD auto to_string(const basic_memory_buffer& buf) -> std::string { auto size = buf.size(); detail::assume(size < std::string().max_size()); @@ -960,8 +894,8 @@ class writer { FILE* file_; public: - writer(FILE* f) : buf_(nullptr), file_(f) {} - writer(detail::buffer& buf) : buf_(&buf) {} + inline writer(FILE* f) : buf_(nullptr), file_(f) {} + inline writer(detail::buffer& buf) : buf_(&buf) {} /// Formats `args` according to specifications in `fmt` and writes the /// output to the file. @@ -979,24 +913,16 @@ class string_buffer { detail::container_buffer buf_; public: - string_buffer() : buf_(str_) {} + inline string_buffer() : buf_(str_) {} - operator writer() { return buf_; } - std::string& str() { return str_; } + inline operator writer() { return buf_; } + inline std::string& str() { return str_; } }; template struct is_contiguous> : std::true_type { }; -FMT_END_EXPORT -namespace detail { -FMT_API auto write_console(int fd, string_view text) -> bool; -FMT_API void print(FILE*, string_view); -} // namespace detail - -FMT_BEGIN_EXPORT - // Suppress a misleading warning in older versions of clang. FMT_PRAGMA_CLANG(diagnostic ignored "-Wweak-vtables") @@ -1006,124 +932,36 @@ class FMT_SO_VISIBILITY("default") format_error : public std::runtime_error { using std::runtime_error::runtime_error; }; -namespace detail_exported { -#if FMT_USE_NONTYPE_TEMPLATE_ARGS +class loc_value; + +FMT_END_EXPORT +namespace detail { +FMT_API auto write_console(int fd, string_view text) -> bool; +FMT_API void print(FILE*, string_view); +} // namespace detail + +namespace detail { template struct fixed_string { - constexpr fixed_string(const Char (&str)[N]) { - detail::copy(static_cast(str), - str + N, data); + FMT_CONSTEXPR20 fixed_string(const Char (&s)[N]) { + detail::copy(static_cast(s), s + N, + data); } Char data[N] = {}; }; -#endif // FMT_USE_NONTYPE_TEMPLATE_ARGS // Converts a compile-time string to basic_string_view. -template +FMT_EXPORT template constexpr auto compile_string_to_view(const Char (&s)[N]) -> basic_string_view { // Remove trailing NUL character if needed. Won't be present if this is used // with a raw character array (i.e. not defined as a string). return {s, N - (std::char_traits::to_int_type(s[N - 1]) == 0 ? 1 : 0)}; } -template +FMT_EXPORT template constexpr auto compile_string_to_view(basic_string_view s) -> basic_string_view { return s; } -} // namespace detail_exported - -// A generic formatting context with custom output iterator and character -// (code unit) support. Char is the format string code unit type which can be -// different from OutputIt::value_type. -template class generic_context { - private: - OutputIt out_; - basic_format_args args_; - detail::locale_ref loc_; - - public: - using char_type = Char; - using iterator = OutputIt; - using parse_context_type FMT_DEPRECATED = parse_context; - template - using formatter_type FMT_DEPRECATED = formatter; - enum { builtin_types = FMT_BUILTIN_TYPES }; - - constexpr generic_context(OutputIt out, - basic_format_args args, - detail::locale_ref loc = {}) - : out_(out), args_(args), loc_(loc) {} - generic_context(generic_context&&) = default; - generic_context(const generic_context&) = delete; - void operator=(const generic_context&) = delete; - - constexpr auto arg(int id) const -> basic_format_arg { - return args_.get(id); - } - auto arg(basic_string_view name) -> basic_format_arg { - return args_.get(name); - } - FMT_CONSTEXPR auto arg_id(basic_string_view name) -> int { - return args_.get_id(name); - } - - FMT_CONSTEXPR auto out() -> iterator { return out_; } - - void advance_to(iterator it) { - if (!detail::is_back_insert_iterator()) out_ = it; - } - - FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } -}; - -class loc_value { - private: - basic_format_arg value_; - - public: - template ::value)> - loc_value(T value) : value_(value) {} - - template ::value)> - loc_value(T) {} - - template auto visit(Visitor&& vis) -> decltype(vis(0)) { - return value_.visit(vis); - } -}; - -// A locale facet that formats values in UTF-8. -// It is parameterized on the locale to avoid the heavy include. -template class format_facet : public Locale::facet { - private: - std::string separator_; - std::string grouping_; - std::string decimal_point_; - - protected: - virtual auto do_put(appender out, loc_value val, - const format_specs& specs) const -> bool; - - public: - static FMT_API typename Locale::id id; - - explicit format_facet(Locale& loc); - explicit format_facet(string_view sep = "", - std::initializer_list g = {3}, - std::string decimal_point = ".") - : separator_(sep.data(), sep.size()), - grouping_(g.begin(), g.end()), - decimal_point_(decimal_point) {} - - auto put(appender out, loc_value val, const format_specs& specs) const - -> bool { - return do_put(out, val, specs); - } -}; - -FMT_END_EXPORT - -namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -1302,6 +1140,17 @@ template <> inline auto decimal_point(locale_ref loc) -> wchar_t { return decimal_point_impl(loc); } +#ifndef FMT_HEADER_ONLY +FMT_BEGIN_EXPORT +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto thousands_sep_impl(locale_ref) + -> thousands_sep_result; +extern template FMT_API auto decimal_point_impl(locale_ref) -> char; +extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; +FMT_END_EXPORT +#endif // FMT_HEADER_ONLY + // Compares two characters for equality. template auto equal2(const Char* lhs, const char* rhs) -> bool { return lhs[0] == Char(rhs[0]) && lhs[1] == Char(rhs[1]); @@ -1412,10 +1261,12 @@ class utf8_to_utf16 { public: FMT_API explicit utf8_to_utf16(string_view s); - operator basic_string_view() const { return {&buffer_[0], size()}; } - auto size() const -> size_t { return buffer_.size() - 1; } - auto c_str() const -> const wchar_t* { return &buffer_[0]; } - auto str() const -> std::wstring { return {&buffer_[0], size()}; } + inline operator basic_string_view() const { + return {&buffer_[0], size()}; + } + inline auto size() const -> size_t { return buffer_.size() - 1; } + inline auto c_str() const -> const wchar_t* { return &buffer_[0]; } + inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } }; enum class to_utf8_error_policy { abort, replace }; @@ -1830,7 +1681,7 @@ FMT_API auto is_printable(uint32_t cp) -> bool; inline auto needs_escape(uint32_t cp) -> bool { if (cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\') return true; - if (FMT_OPTIMIZE_SIZE > 1) return false; + if (const_check(FMT_OPTIMIZE_SIZE > 1)) return false; return !is_printable(cp); } @@ -1853,7 +1704,7 @@ auto find_escape(const Char* begin, const Char* end) inline auto find_escape(const char* begin, const char* end) -> find_escape_result { - if (!detail::use_utf8) return find_escape(begin, end); + if (const_check(!use_utf8)) return find_escape(begin, end); auto result = find_escape_result{end, nullptr, 0}; for_each_codepoint(string_view(begin, to_unsigned(end - begin)), [&](uint32_t cp, string_view sv) { @@ -2090,8 +1941,8 @@ FMT_API auto write_loc(appender out, loc_value value, const format_specs& specs, locale_ref loc) -> bool; #endif template -inline auto write_loc(OutputIt, loc_value, const format_specs&, locale_ref) - -> bool { +inline auto write_loc(OutputIt, const loc_value&, const format_specs&, + locale_ref) -> bool { return false; } @@ -2551,7 +2402,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, } else if (exp > 0) { // 1234e-2 -> 12.34[0+] int num_zeros = specs.alt() ? specs.precision - significand_size : 0; - size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0); + size += 1 + static_cast(max_of(num_zeros, 0)); auto grouping = Grouping(loc, specs.localized()); size += to_unsigned(grouping.count_separators(exp)); return write_padded(out, specs, size, [&](iterator it) { @@ -2654,52 +2505,48 @@ inline FMT_CONSTEXPR20 void adjust_precision(int& precision, int exp10) { class bigint { private: - // A bigint is stored as an array of bigits (big digits), with bigit at index - // 0 being the least significant one. - using bigit = uint32_t; + // A bigint is a number in the form bigit_[N - 1] ... bigit_[0] * 32^exp_. + using bigit = uint32_t; // A big digit. using double_bigit = uint64_t; + enum { bigit_bits = num_bits() }; enum { bigits_capacity = 32 }; basic_memory_buffer bigits_; int exp_; - FMT_CONSTEXPR20 auto operator[](int index) const -> bigit { - return bigits_[to_unsigned(index)]; - } - FMT_CONSTEXPR20 auto operator[](int index) -> bigit& { - return bigits_[to_unsigned(index)]; - } - - static constexpr const int bigit_bits = num_bits(); - friend struct formatter; - FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast((*this)[index]) - other - borrow; - (*this)[index] = static_cast(result); + FMT_CONSTEXPR auto get_bigit(int i) const -> bigit { + return i >= exp_ && i < num_bigits() ? bigits_[i - exp_] : 0; + } + + FMT_CONSTEXPR void subtract_bigits(int index, bigit other, bigit& borrow) { + auto result = double_bigit(bigits_[index]) - other - borrow; + bigits_[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } - FMT_CONSTEXPR20 void remove_leading_zeros() { + FMT_CONSTEXPR void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits; + while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; bigits_.resize(to_unsigned(num_bigits + 1)); } // Computes *this -= other assuming aligned bigints and *this >= other. - FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) { + FMT_CONSTEXPR void subtract_aligned(const bigint& other) { FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints"); FMT_ASSERT(compare(*this, other) >= 0, ""); bigit borrow = 0; int i = other.exp_ - exp_; for (size_t j = 0, n = other.bigits_.size(); j != n; ++i, ++j) subtract_bigits(i, other.bigits_[j], borrow); - while (borrow > 0) subtract_bigits(i, 0, borrow); + if (borrow != 0) subtract_bigits(i, 0, borrow); + FMT_ASSERT(borrow == 0, ""); remove_leading_zeros(); } - FMT_CONSTEXPR20 void multiply(uint32_t value) { - const double_bigit wide_value = value; + FMT_CONSTEXPR void multiply(uint32_t value) { bigit carry = 0; + const double_bigit wide_value = value; for (size_t i = 0, n = bigits_.size(); i < n; ++i) { double_bigit result = bigits_[i] * wide_value + carry; bigits_[i] = static_cast(result); @@ -2710,7 +2557,7 @@ class bigint { template ::value || std::is_same::value)> - FMT_CONSTEXPR20 void multiply(UInt value) { + FMT_CONSTEXPR void multiply(UInt value) { using half_uint = conditional_t::value, uint64_t, uint32_t>; const int shift = num_bits() - bigit_bits; @@ -2731,7 +2578,7 @@ class bigint { template ::value || std::is_same::value)> - FMT_CONSTEXPR20 void assign(UInt n) { + FMT_CONSTEXPR void assign(UInt n) { size_t num_bigits = 0; do { bigits_[num_bigits++] = static_cast(n); @@ -2742,13 +2589,13 @@ class bigint { } public: - FMT_CONSTEXPR20 bigint() : exp_(0) {} + FMT_CONSTEXPR bigint() : exp_(0) {} explicit bigint(uint64_t n) { assign(n); } bigint(const bigint&) = delete; void operator=(const bigint&) = delete; - FMT_CONSTEXPR20 void assign(const bigint& other) { + FMT_CONSTEXPR void assign(const bigint& other) { auto size = other.bigits_.size(); bigits_.resize(size); auto data = other.bigits_.data(); @@ -2756,16 +2603,16 @@ class bigint { exp_ = other.exp_; } - template FMT_CONSTEXPR20 void operator=(Int n) { + template FMT_CONSTEXPR void operator=(Int n) { FMT_ASSERT(n > 0, ""); assign(uint64_or_128_t(n)); } - FMT_CONSTEXPR20 auto num_bigits() const -> int { + FMT_CONSTEXPR auto num_bigits() const -> int { return static_cast(bigits_.size()) + exp_; } - FMT_NOINLINE FMT_CONSTEXPR20 auto operator<<=(int shift) -> bigint& { + FMT_CONSTEXPR auto operator<<=(int shift) -> bigint& { FMT_ASSERT(shift >= 0, ""); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -2780,49 +2627,39 @@ class bigint { return *this; } - template - FMT_CONSTEXPR20 auto operator*=(Int value) -> bigint& { + template FMT_CONSTEXPR auto operator*=(Int value) -> bigint& { FMT_ASSERT(value > 0, ""); multiply(uint32_or_64_or_128_t(value)); return *this; } - friend FMT_CONSTEXPR20 auto compare(const bigint& lhs, const bigint& rhs) - -> int { - int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits(); - if (num_lhs_bigits != num_rhs_bigits) - return num_lhs_bigits > num_rhs_bigits ? 1 : -1; - int i = static_cast(lhs.bigits_.size()) - 1; - int j = static_cast(rhs.bigits_.size()) - 1; + friend FMT_CONSTEXPR auto compare(const bigint& b1, const bigint& b2) -> int { + int num_bigits1 = b1.num_bigits(), num_bigits2 = b2.num_bigits(); + if (num_bigits1 != num_bigits2) return num_bigits1 > num_bigits2 ? 1 : -1; + int i = static_cast(b1.bigits_.size()) - 1; + int j = static_cast(b2.bigits_.size()) - 1; int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; - if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; + bigit b1_bigit = b1.bigits_[i], b2_bigit = b2.bigits_[j]; + if (b1_bigit != b2_bigit) return b1_bigit > b2_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; return 0; } // Returns compare(lhs1 + lhs2, rhs). - friend FMT_CONSTEXPR20 auto add_compare(const bigint& lhs1, - const bigint& lhs2, const bigint& rhs) - -> int { - auto minimum = [](int a, int b) { return a < b ? a : b; }; - auto maximum = [](int a, int b) { return a > b ? a : b; }; - int max_lhs_bigits = maximum(lhs1.num_bigits(), lhs2.num_bigits()); + friend FMT_CONSTEXPR auto add_compare(const bigint& lhs1, const bigint& lhs2, + const bigint& rhs) -> int { + int max_lhs_bigits = max_of(lhs1.num_bigits(), lhs2.num_bigits()); int num_rhs_bigits = rhs.num_bigits(); if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; if (max_lhs_bigits > num_rhs_bigits) return 1; - auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; - }; double_bigit borrow = 0; - int min_exp = minimum(minimum(lhs1.exp_, lhs2.exp_), rhs.exp_); + int min_exp = min_of(min_of(lhs1.exp_, lhs2.exp_), rhs.exp_); for (int i = num_rhs_bigits - 1; i >= min_exp; --i) { - double_bigit sum = - static_cast(get_bigit(lhs1, i)) + get_bigit(lhs2, i); - bigit rhs_bigit = get_bigit(rhs, i); + double_bigit sum = double_bigit(lhs1.get_bigit(i)) + lhs2.get_bigit(i); + bigit rhs_bigit = rhs.get_bigit(i); if (sum > rhs_bigit + borrow) return 1; borrow = rhs_bigit + borrow - sum; if (borrow > 1) return -1; @@ -2835,10 +2672,8 @@ class bigint { FMT_CONSTEXPR20 void assign_pow10(int exp) { FMT_ASSERT(exp >= 0, ""); if (exp == 0) return *this = 1; - // Find the top bit. - int bitmask = 1; - while (exp >= bitmask) bitmask <<= 1; - bitmask >>= 1; + int bitmask = 1 << (num_bits() - + countl_zero(static_cast(exp)) - 1); // pow(10, exp) = pow(5, exp) * pow(2, exp). First compute pow(5, exp) by // repeated squaring and multiplication. *this = 5; @@ -2862,17 +2697,17 @@ class bigint { // cross-product terms n[i] * n[j] such that i + j == bigit_index. for (int i = 0, j = bigit_index; j >= 0; ++i, --j) { // Most terms are multiplied twice which can be optimized in the future. - sum += static_cast(n[i]) * n[j]; + sum += double_bigit(n[i]) * n[j]; } - (*this)[bigit_index] = static_cast(sum); + bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); // Compute the carry. } // Do the same for the top half. for (int bigit_index = num_bigits; bigit_index < num_result_bigits; ++bigit_index) { for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) - sum += static_cast(n[i++]) * n[j--]; - (*this)[bigit_index] = static_cast(sum); + sum += double_bigit(n[i++]) * n[j--]; + bigits_[bigit_index] = static_cast(sum); sum >>= num_bits(); } remove_leading_zeros(); @@ -2881,7 +2716,7 @@ class bigint { // If this bigint has a bigger exponent than other, adds trailing zero to make // exponents equal. This simplifies some operations such as subtraction. - FMT_CONSTEXPR20 void align(const bigint& other) { + FMT_CONSTEXPR void align(const bigint& other) { int exp_difference = exp_ - other.exp_; if (exp_difference <= 0) return; int num_bigits = static_cast(bigits_.size()); @@ -2894,7 +2729,7 @@ class bigint { // Divides this bignum by divisor, assigning the remainder to this and // returning the quotient. - FMT_CONSTEXPR20 auto divmod_assign(const bigint& divisor) -> int { + FMT_CONSTEXPR auto divmod_assign(const bigint& divisor) -> int { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); @@ -3282,7 +3117,7 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, uint64_t prod; uint32_t digits; bool should_round_up; - int number_of_digits_to_print = precision > 9 ? 9 : precision; + int number_of_digits_to_print = min_of(precision, 9); // Print a 9-digits subsegment, either the first or the second. auto print_subsegment = [&](uint32_t subsegment, char* buffer) { @@ -3704,7 +3539,7 @@ FMT_CONSTEXPR void handle_dynamic_spec( #if FMT_USE_NONTYPE_TEMPLATE_ARGS template Str> + fmt::detail::fixed_string Str> struct static_named_arg : view { static constexpr auto name = Str.data; @@ -3713,16 +3548,15 @@ struct static_named_arg : view { }; template Str> + fmt::detail::fixed_string Str> struct is_named_arg> : std::true_type {}; template Str> + fmt::detail::fixed_string Str> struct is_static_named_arg> : std::true_type { }; -template Str> +template Str> struct udl_arg { template auto operator=(T&& value) const { return static_named_arg(std::forward(value)); @@ -3784,6 +3618,27 @@ template struct format_handler { FMT_NORETURN void on_error(const char* message) { report_error(message); } }; +using format_func = void (*)(detail::buffer&, int, const char*); +FMT_API void do_report_error(format_func func, int error_code, + const char* message) noexcept; + +FMT_API void format_error_code(buffer& out, int error_code, + string_view message) noexcept; + +template +template +FMT_CONSTEXPR auto native_formatter::format( + const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { + if (!specs_.dynamic()) + return write(ctx.out(), val, specs_, ctx.locale()); + auto specs = format_specs(specs_); + handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, + ctx); + handle_dynamic_spec(specs.dynamic_precision(), specs.precision, + specs_.precision_ref, ctx); + return write(ctx.out(), val, specs, ctx.locale()); +} + // DEPRECATED! template struct vformat_args { using type = basic_format_args>; @@ -3799,52 +3654,96 @@ void vformat_to(buffer& buf, basic_string_view fmt, parse_format_string( fmt, format_handler{parse_context(fmt), {out, args, loc}}); } - -using format_func = void (*)(detail::buffer&, int, const char*); - -FMT_API void format_error_code(buffer& out, int error_code, - string_view message) noexcept; - -using fmt::report_error; -FMT_API void report_error(format_func func, int error_code, - const char* message) noexcept; +} // namespace detail FMT_BEGIN_EXPORT -#ifndef FMT_HEADER_ONLY -extern template FMT_API auto thousands_sep_impl(locale_ref) - -> thousands_sep_result; -extern template FMT_API auto thousands_sep_impl(locale_ref) - -> thousands_sep_result; -extern template FMT_API auto decimal_point_impl(locale_ref) -> char; -extern template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t; -#endif // FMT_HEADER_ONLY +// A generic formatting context with custom output iterator and character +// (code unit) support. Char is the format string code unit type which can be +// different from OutputIt::value_type. +template class generic_context { + private: + OutputIt out_; + basic_format_args args_; + detail::locale_ref loc_; -FMT_END_EXPORT + public: + using char_type = Char; + using iterator = OutputIt; + using parse_context_type FMT_DEPRECATED = parse_context; + template + using formatter_type FMT_DEPRECATED = formatter; + enum { builtin_types = FMT_BUILTIN_TYPES }; -template -template -FMT_CONSTEXPR auto native_formatter::format( - const T& val, FormatContext& ctx) const -> decltype(ctx.out()) { - if (!specs_.dynamic()) - return write(ctx.out(), val, specs_, ctx.locale()); - auto specs = format_specs(specs_); - handle_dynamic_spec(specs.dynamic_width(), specs.width, specs_.width_ref, - ctx); - handle_dynamic_spec(specs.dynamic_precision(), specs.precision, - specs_.precision_ref, ctx); - return write(ctx.out(), val, specs, ctx.locale()); -} -} // namespace detail + constexpr generic_context(OutputIt out, + basic_format_args args, + detail::locale_ref loc = {}) + : out_(out), args_(args), loc_(loc) {} + generic_context(generic_context&&) = default; + generic_context(const generic_context&) = delete; + void operator=(const generic_context&) = delete; -template -struct formatter>> - : formatter, Char> { - template - FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const - -> decltype(ctx.out()) { - auto&& val = format_as(value); // Make an lvalue reference for format. - return formatter, Char>::format(val, ctx); + constexpr auto arg(int id) const -> basic_format_arg { + return args_.get(id); + } + auto arg(basic_string_view name) const + -> basic_format_arg { + return args_.get(name); + } + constexpr auto arg_id(basic_string_view name) const -> int { + return args_.get_id(name); + } + + constexpr auto out() const -> iterator { return out_; } + + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } + + constexpr auto locale() const -> detail::locale_ref { return loc_; } +}; + +class loc_value { + private: + basic_format_arg value_; + + public: + template ::value)> + loc_value(T value) : value_(value) {} + + template ::value)> + loc_value(T) {} + + template auto visit(Visitor&& vis) -> decltype(vis(0)) { + return value_.visit(vis); + } +}; + +// A locale facet that formats values in UTF-8. +// It is parameterized on the locale to avoid the heavy include. +template class format_facet : public Locale::facet { + private: + std::string separator_; + std::string grouping_; + std::string decimal_point_; + + protected: + virtual auto do_put(appender out, loc_value val, + const format_specs& specs) const -> bool; + + public: + static FMT_API typename Locale::id id; + + explicit format_facet(Locale& loc); + explicit format_facet(string_view sep = "", std::string grouping = "\3", + std::string decimal_point = ".") + : separator_(sep.data(), sep.size()), + grouping_(grouping), + decimal_point_(decimal_point) {} + + auto put(appender out, loc_value val, const format_specs& specs) const + -> bool { + return do_put(out, val, specs); } }; @@ -3887,6 +3786,17 @@ struct formatter : detail::native_formatter {}; +template +struct formatter>> + : formatter, Char> { + template + FMT_CONSTEXPR auto format(const T& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto&& val = format_as(value); // Make an lvalue reference for format. + return formatter, Char>::format(val, ctx); + } +}; + /** * Converts `p` to `const void*` for pointer formatting. * @@ -3905,7 +3815,7 @@ template auto ptr(T p) -> const void* { * **Example**: * * enum class color { red, green, blue }; - * auto s = fmt::format("{}", fmt::underlying(color::red)); + * auto s = fmt::format("{}", fmt::underlying(color::red)); // s == "0" */ template constexpr auto underlying(Enum e) noexcept -> underlying_t { @@ -3934,7 +3844,7 @@ template <> struct formatter : formatter { struct bytes { string_view data; - explicit bytes(string_view s) : data(s) {} + inline explicit bytes(string_view s) : data(s) {} }; template <> struct formatter { @@ -3987,14 +3897,14 @@ template struct formatter> : formatter { } template - auto format(group_digits_view t, FormatContext& ctx) const + auto format(group_digits_view view, FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, specs.width_ref, ctx); detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision, specs.precision_ref, ctx); - auto arg = detail::make_write_int_arg(t.value, specs.sign()); + auto arg = detail::make_write_int_arg(view.value, specs.sign()); return detail::write_int( ctx.out(), static_cast>(arg.abs_value), arg.prefix, specs, detail::digit_grouping("\3", ",")); @@ -4050,8 +3960,7 @@ template struct nested_formatter { write(basic_appender(buf)); auto specs = format_specs(); specs.width = width_; - specs.set_fill( - basic_string_view(specs_.fill(), specs_.fill_size())); + specs.set_fill(specs_); specs.set_align(specs_.align()); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); @@ -4064,9 +3973,9 @@ template struct nested_formatter { inline namespace literals { #if FMT_USE_NONTYPE_TEMPLATE_ARGS -template constexpr auto operator""_a() { - using char_t = remove_cvref_t; - return detail::udl_arg(); +template constexpr auto operator""_a() { + using char_t = remove_cvref_t; + return detail::udl_arg(); } #else /** @@ -4109,16 +4018,16 @@ class format_int { } public: - explicit FMT_CONSTEXPR20 format_int(int value) : str_(format_signed(value)) {} - explicit FMT_CONSTEXPR20 format_int(long value) + FMT_CONSTEXPR20 explicit format_int(int value) : str_(format_signed(value)) {} + FMT_CONSTEXPR20 explicit format_int(long value) : str_(format_signed(value)) {} - explicit FMT_CONSTEXPR20 format_int(long long value) + FMT_CONSTEXPR20 explicit format_int(long long value) : str_(format_signed(value)) {} - explicit FMT_CONSTEXPR20 format_int(unsigned value) + FMT_CONSTEXPR20 explicit format_int(unsigned value) : str_(format_unsigned(value)) {} - explicit FMT_CONSTEXPR20 format_int(unsigned long value) + FMT_CONSTEXPR20 explicit format_int(unsigned long value) : str_(format_unsigned(value)) {} - explicit FMT_CONSTEXPR20 format_int(unsigned long long value) + FMT_CONSTEXPR20 explicit format_int(unsigned long long value) : str_(format_unsigned(value)) {} /// Returns the number of characters written to the output buffer. @@ -4138,28 +4047,27 @@ class format_int { } /// Returns the content of the output buffer as an `std::string`. - auto str() const -> std::string { return {str_, size()}; } + inline auto str() const -> std::string { return {str_, size()}; } }; -#define FMT_STRING_IMPL(s, base) \ - [] { \ - /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ - /* Use a macro-like name to avoid shadowing warnings. */ \ - struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ - using char_type = fmt::remove_cvref_t; \ - FMT_CONSTEXPR explicit operator fmt::basic_string_view() \ - const { \ - return fmt::detail_exported::compile_string_to_view(s); \ - } \ - }; \ - using FMT_STRING_VIEW = \ - fmt::basic_string_view; \ - fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ - return FMT_COMPILE_STRING(); \ +#define FMT_STRING_IMPL(s, base) \ + [] { \ + /* Use the hidden visibility as a workaround for a GCC bug (#1973). */ \ + /* Use a macro-like name to avoid shadowing warnings. */ \ + struct FMT_VISIBILITY("hidden") FMT_COMPILE_STRING : base { \ + using char_type = fmt::remove_cvref_t; \ + constexpr explicit operator fmt::basic_string_view() const { \ + return fmt::detail::compile_string_to_view(s); \ + } \ + }; \ + using FMT_STRING_VIEW = \ + fmt::basic_string_view; \ + fmt::detail::ignore_unused(FMT_STRING_VIEW(FMT_COMPILE_STRING())); \ + return FMT_COMPILE_STRING(); \ }() /** - * Constructs a compile-time format string from a string literal `s`. + * Constructs a legacy compile-time format string from a string literal `s`. * * **Example**: * @@ -4168,7 +4076,6 @@ class format_int { */ #define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string) -FMT_BEGIN_EXPORT FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) -> std::system_error; @@ -4213,47 +4120,41 @@ FMT_API void format_system_error(detail::buffer& out, int error_code, // Can be used to report errors from destructors. FMT_API void report_system_error(int error_code, const char* message) noexcept; -template ::value)> -auto vformat(const Locale& loc, string_view fmt, format_args args) +inline auto vformat(detail::locale_ref loc, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); - detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); + detail::vformat_to(buf, fmt, args, loc); return {buf.data(), buf.size()}; } -template ::value)> -FMT_INLINE auto format(const Locale& loc, format_string fmt, T&&... args) - -> std::string { - return fmt::vformat(loc, fmt.str, vargs{{args...}}); +template +FMT_INLINE auto format(detail::locale_ref loc, format_string fmt, + T&&... args) -> std::string { + return vformat(loc, fmt.str, vargs{{args...}}); } -template ::value&& - detail::is_locale::value)> -auto vformat_to(OutputIt out, const Locale& loc, string_view fmt, +template ::value)> +auto vformat_to(OutputIt out, detail::locale_ref loc, string_view fmt, format_args args) -> OutputIt { auto&& buf = detail::get_buffer(out); - detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); + detail::vformat_to(buf, fmt, args, loc); return detail::get_iterator(buf, out); } -template ::value&& - detail::is_locale::value)> -FMT_INLINE auto format_to(OutputIt out, const Locale& loc, +template ::value)> +FMT_INLINE auto format_to(OutputIt out, detail::locale_ref loc, format_string fmt, T&&... args) -> OutputIt { return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); } -template ::value)> -FMT_NODISCARD FMT_INLINE auto formatted_size(const Locale& loc, +template +FMT_NODISCARD FMT_INLINE auto formatted_size(detail::locale_ref loc, format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, fmt.str, vargs{{args...}}, - detail::locale_ref(loc)); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); return buf.count(); } @@ -4285,10 +4186,8 @@ template ::value)> FMT_NODISCARD auto to_string(T value) -> std::string { // The buffer should be large enough to store the number including the sign // or "false" for bool. - constexpr int max_size = detail::digits10() + 2; - char buffer[max_size > 5 ? static_cast(max_size) : 5]; - char* begin = buffer; - return {buffer, detail::write(begin, value)}; + char buffer[max_of(detail::digits10() + 2, 5)]; + return {buffer, detail::write(buffer, value)}; } template ::value)> @@ -4310,8 +4209,6 @@ FMT_END_NAMESPACE #ifdef FMT_HEADER_ONLY # define FMT_FUNC inline # include "format-inl.h" -#else -# define FMT_FUNC #endif // Restore _LIBCPP_REMOVE_TRANSITIVE_INCLUDES. diff --git a/include/fmt/os.h b/include/fmt/os.h index 75b47c69..b2cc5e4b 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -176,24 +176,24 @@ class buffered_file { friend class file; - explicit buffered_file(FILE* f) : file_(f) {} + inline explicit buffered_file(FILE* f) : file_(f) {} public: buffered_file(const buffered_file&) = delete; void operator=(const buffered_file&) = delete; // Constructs a buffered_file object which doesn't represent any file. - buffered_file() noexcept : file_(nullptr) {} + inline buffered_file() noexcept : file_(nullptr) {} // Destroys the object closing the file it represents if any. FMT_API ~buffered_file() noexcept; public: - buffered_file(buffered_file&& other) noexcept : file_(other.file_) { + inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) { other.file_ = nullptr; } - auto operator=(buffered_file&& other) -> buffered_file& { + inline auto operator=(buffered_file&& other) -> buffered_file& { close(); file_ = other.file_; other.file_ = nullptr; @@ -207,7 +207,7 @@ class buffered_file { FMT_API void close(); // Returns the pointer to a FILE object representing this file. - auto get() const noexcept -> FILE* { return file_; } + inline auto get() const noexcept -> FILE* { return file_; } FMT_API auto descriptor() const -> int; @@ -248,7 +248,7 @@ class FMT_API file { }; // Constructs a file object which doesn't represent any file. - file() noexcept : fd_(-1) {} + inline file() noexcept : fd_(-1) {} // Opens a file and constructs a file object representing this file. file(cstring_view path, int oflag); @@ -257,10 +257,10 @@ class FMT_API file { file(const file&) = delete; void operator=(const file&) = delete; - file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } + inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } // Move assignment is not noexcept because close may throw. - auto operator=(file&& other) -> file& { + inline auto operator=(file&& other) -> file& { close(); fd_ = other.fd_; other.fd_ = -1; @@ -271,7 +271,7 @@ class FMT_API file { ~file() noexcept; // Returns the file descriptor. - auto descriptor() const noexcept -> int { return fd_; } + inline auto descriptor() const noexcept -> int { return fd_; } // Closes the file. void close(); @@ -324,9 +324,9 @@ auto getpagesize() -> long; namespace detail { struct buffer_size { - buffer_size() = default; + constexpr buffer_size() = default; size_t value = 0; - auto operator=(size_t val) const -> buffer_size { + FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size { auto bs = buffer_size(); bs.value = val; return bs; @@ -337,7 +337,7 @@ struct ostream_params { int oflag = file::WRONLY | file::CREATE | file::TRUNC; size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; - ostream_params() {} + constexpr ostream_params() {} template ostream_params(T... params, int new_oflag) : ostream_params(params...) { @@ -381,7 +381,7 @@ class FMT_API ostream : private detail::buffer { return buf; } - void flush() { + inline void flush() { if (size() == 0) return; file_.write(data(), size() * sizeof(data()[0])); clear(); @@ -390,7 +390,7 @@ class FMT_API ostream : private detail::buffer { template friend auto output_file(cstring_view path, T... params) -> ostream; - void close() { + inline void close() { flush(); file_.close(); } diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 114b8c29..5d893c92 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -22,6 +22,14 @@ #include "chrono.h" // formatbuf +#ifdef _MSVC_STL_UPDATE +# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE +#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5 +# define FMT_MSVC_STL_UPDATE _MSVC_LANG +#else +# define FMT_MSVC_STL_UPDATE 0 +#endif + FMT_BEGIN_NAMESPACE namespace detail { @@ -35,7 +43,7 @@ class file_access { friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } }; -#if FMT_MSC_VERSION +#if FMT_MSVC_STL_UPDATE template class file_access; auto get_file(std::filebuf&) -> FILE*; @@ -109,7 +117,7 @@ inline void vprint(std::ostream& os, string_view fmt, format_args args) { auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt, args); FILE* f = nullptr; -#if FMT_MSC_VERSION && FMT_USE_RTTI +#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI if (auto* buf = dynamic_cast(os.rdbuf())) f = detail::get_file(*buf); #elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 7a1c5081..e7268401 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -79,7 +79,7 @@ template struct int_checker { unsigned max = to_unsigned(max_value()); return value <= max; } - static auto fits_in_int(bool) -> bool { return true; } + inline static auto fits_in_int(bool) -> bool { return true; } }; template <> struct int_checker { @@ -87,7 +87,7 @@ template <> struct int_checker { return value >= (std::numeric_limits::min)() && value <= max_value(); } - static auto fits_in_int(int) -> bool { return true; } + inline static auto fits_in_int(int) -> bool { return true; } }; struct printf_precision_handler { @@ -205,7 +205,7 @@ class printf_width_handler { format_specs& specs_; public: - explicit printf_width_handler(format_specs& specs) : specs_(specs) {} + inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {} template ::value)> auto operator()(T value) -> unsigned { diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index c6020abe..118d24fe 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -357,12 +357,9 @@ template using maybe_const_range = conditional_t::value, const R, R>; -// Workaround a bug in MSVC 2015 and earlier. -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 template struct is_formattable_delayed : is_formattable>, Char> {}; -#endif } // namespace detail template struct conjunction : std::true_type {}; @@ -498,13 +495,8 @@ struct formatter< range_format_kind::value != range_format::disabled && range_format_kind::value != range_format::map && range_format_kind::value != range_format::string && - range_format_kind::value != range_format::debug_string> -// Workaround a bug in MSVC 2015 and earlier. -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 - , - detail::is_formattable_delayed -#endif - >::value>> { + range_format_kind::value != range_format::debug_string>, + detail::is_formattable_delayed>::value>> { private: using range_type = detail::maybe_const_range; range_formatter, Char> range_formatter_; @@ -646,9 +638,9 @@ struct formatter, Char> { #endif formatter, Char> value_formatter_; - using view_ref = conditional_t::value, - const join_view&, - join_view&&>; + using view = conditional_t::value, + const join_view, + join_view>; public: using nonlocking = void; @@ -658,9 +650,10 @@ struct formatter, Char> { } template - auto format(view_ref& value, FormatContext& ctx) const - -> decltype(ctx.out()) { - auto it = std::forward(value).begin; + auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) { + using iter = + conditional_t::value, It, It&>; + iter it = value.begin; auto out = ctx.out(); if (it == value.end) return out; out = value_formatter_.format(*it, ctx); @@ -675,39 +668,11 @@ struct formatter, Char> { } }; -/// Returns a view that formats the iterator range `[begin, end)` with elements -/// separated by `sep`. -template -auto join(It begin, Sentinel end, string_view sep) -> join_view { - return {std::move(begin), end, sep}; -} - -/** - * Returns a view that formats `range` with elements separated by `sep`. - * - * **Example**: - * - * auto v = std::vector{1, 2, 3}; - * fmt::print("{}", fmt::join(v, ", ")); - * // Output: 1, 2, 3 - * - * `fmt::join` applies passed format specifiers to the range elements: - * - * fmt::print("{:02}", fmt::join(v, ", ")); - * // Output: 01, 02, 03 - */ -template -auto join(Range&& r, string_view sep) - -> join_view { - return {detail::range_begin(r), detail::range_end(r), sep}; -} - -template struct tuple_join_view : detail::view { - const std::tuple& tuple; +template struct tuple_join_view : detail::view { + const Tuple& tuple; basic_string_view sep; - tuple_join_view(const std::tuple& t, basic_string_view s) + tuple_join_view(const Tuple& t, basic_string_view s) : tuple(t), sep{s} {} }; @@ -718,21 +683,22 @@ template struct tuple_join_view : detail::view { # define FMT_TUPLE_JOIN_SPECIFIERS 0 #endif -template -struct formatter, Char> { +template +struct formatter, Char, + enable_if_t::value>> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { - return do_parse(ctx, std::integral_constant()); + return do_parse(ctx, std::tuple_size()); } template - auto format(const tuple_join_view& value, + auto format(const tuple_join_view& value, FormatContext& ctx) const -> typename FormatContext::iterator { - return do_format(value, ctx, - std::integral_constant()); + return do_format(value, ctx, std::tuple_size()); } private: - std::tuple::type, Char>...> formatters_; + decltype(detail::tuple::get_formatters( + detail::tuple_index_sequence())) formatters_; FMT_CONSTEXPR auto do_parse(parse_context& ctx, std::integral_constant) @@ -746,7 +712,7 @@ struct formatter, Char> { -> const Char* { auto end = ctx.begin(); #if FMT_TUPLE_JOIN_SPECIFIERS - end = std::get(formatters_).parse(ctx); + end = std::get::value - N>(formatters_).parse(ctx); if (N > 1) { auto end1 = do_parse(ctx, std::integral_constant()); if (end != end1) @@ -757,18 +723,20 @@ struct formatter, Char> { } template - auto do_format(const tuple_join_view&, FormatContext& ctx, + auto do_format(const tuple_join_view&, FormatContext& ctx, std::integral_constant) const -> typename FormatContext::iterator { return ctx.out(); } template - auto do_format(const tuple_join_view& value, FormatContext& ctx, + auto do_format(const tuple_join_view& value, FormatContext& ctx, std::integral_constant) const -> typename FormatContext::iterator { - auto out = std::get(formatters_) - .format(std::get(value.tuple), ctx); + using std::get; + auto out = + std::get::value - N>(formatters_) + .format(get::value - N>(value.tuple), ctx); if (N <= 1) return out; out = detail::copy(value.sep, out); ctx.advance_to(out); @@ -816,6 +784,34 @@ struct formatter< FMT_BEGIN_EXPORT +/// Returns a view that formats the iterator range `[begin, end)` with elements +/// separated by `sep`. +template +auto join(It begin, Sentinel end, string_view sep) -> join_view { + return {std::move(begin), end, sep}; +} + +/** + * Returns a view that formats `range` with elements separated by `sep`. + * + * **Example**: + * + * auto v = std::vector{1, 2, 3}; + * fmt::print("{}", fmt::join(v, ", ")); + * // Output: 1, 2, 3 + * + * `fmt::join` applies passed format specifiers to the range elements: + * + * fmt::print("{:02}", fmt::join(v, ", ")); + * // Output: 01, 02, 03 + */ +template ::value)> +auto join(Range&& r, string_view sep) + -> join_view { + return {detail::range_begin(r), detail::range_end(r), sep}; +} + /** * Returns an object that formats `std::tuple` with elements separated by `sep`. * @@ -825,9 +821,9 @@ FMT_BEGIN_EXPORT * fmt::print("{}", fmt::join(t, ", ")); * // Output: 1, a */ -template -FMT_CONSTEXPR auto join(const std::tuple& tuple, string_view sep) - -> tuple_join_view { +template ::value)> +FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) + -> tuple_join_view { return {tuple, sep}; } diff --git a/include/fmt/std.h b/include/fmt/std.h index 722b6ad9..bb07fedc 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -27,7 +27,8 @@ // Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. # if FMT_CPLUSPLUS >= 201703L -# if FMT_HAS_INCLUDE() +# if FMT_HAS_INCLUDE() && \ + (!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0) # include # endif # if FMT_HAS_INCLUDE() @@ -183,7 +184,8 @@ FMT_END_NAMESPACE FMT_BEGIN_NAMESPACE FMT_EXPORT template -struct formatter, Char> : nested_formatter { +struct formatter, Char> + : nested_formatter, Char> { private: // Functor because C++11 doesn't support generic lambdas. struct writer { @@ -203,7 +205,7 @@ struct formatter, Char> : nested_formatter { template auto format(const std::bitset& bs, FormatContext& ctx) const -> decltype(ctx.out()) { - return write_padded(ctx, writer{bs}); + return this->write_padded(ctx, writer{bs}); } }; @@ -433,8 +435,8 @@ template <> struct formatter { } template - FMT_CONSTEXPR20 auto format(const std::error_code& ec, FormatContext& ctx) const - -> decltype(ctx.out()) { + FMT_CONSTEXPR20 auto format(const std::error_code& ec, + FormatContext& ctx) const -> decltype(ctx.out()) { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); @@ -694,9 +696,7 @@ template struct formatter, Char> { auto outer_specs = format_specs(); outer_specs.width = specs.width; - auto fill = specs.template fill(); - if (fill) - outer_specs.set_fill(basic_string_view(fill, specs.fill_size())); + outer_specs.set_fill(specs); outer_specs.set_align(specs.align()); specs.width = 0; diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index da593d33..4cbda542 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -140,7 +140,7 @@ auto join(It begin, Sentinel end, wstring_view sep) return {begin, end, sep}; } -template +template ::value)> auto join(Range&& range, wstring_view sep) -> join_view { @@ -153,9 +153,9 @@ auto join(std::initializer_list list, wstring_view sep) return join(std::begin(list), std::end(list), sep); } -template -auto join(const std::tuple& tuple, basic_string_view sep) - -> tuple_join_view { +template ::value)> +auto join(const Tuple& tuple, basic_string_view sep) + -> tuple_join_view { return {tuple, sep}; } @@ -191,11 +191,9 @@ auto format(const S& fmt, T&&... args) -> std::basic_string { fmt::make_format_args>(args...)); } -template , - FMT_ENABLE_IF(detail::is_locale::value&& - detail::is_exotic_char::value)> -inline auto vformat(const Locale& loc, const S& fmt, +template , + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto vformat(detail::locale_ref loc, const S& fmt, typename detail::vformat_args::type args) -> std::basic_string { auto buf = basic_memory_buffer(); @@ -204,11 +202,10 @@ inline auto vformat(const Locale& loc, const S& fmt, return {buf.data(), buf.size()}; } -template , - FMT_ENABLE_IF(detail::is_locale::value&& - detail::is_exotic_char::value)> -inline auto format(const Locale& loc, const S& fmt, T&&... args) + FMT_ENABLE_IF(detail::is_exotic_char::value)> +inline auto format(detail::locale_ref loc, const S& fmt, T&&... args) -> std::basic_string { return vformat(loc, detail::to_string_view(fmt), fmt::make_format_args>(args...)); @@ -235,12 +232,11 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt { fmt::make_format_args>(args...)); } -template , FMT_ENABLE_IF(detail::is_output_iterator::value&& - detail::is_locale::value&& - detail::is_exotic_char::value)> -inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt, + detail::is_exotic_char::value)> +inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt, typename detail::vformat_args::type args) -> OutputIt { auto&& buf = detail::get_buffer(out); @@ -248,12 +244,11 @@ inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt, return detail::get_iterator(buf, out); } -template , bool enable = detail::is_output_iterator::value && - detail::is_locale::value && detail::is_exotic_char::value> -inline auto format_to(OutputIt out, const Locale& loc, const S& fmt, +inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt, T&&... args) -> typename std::enable_if::type { return vformat_to(out, loc, detail::to_string_view(fmt), diff --git a/src/format.cc b/src/format.cc index 255c0433..3ccd8068 100644 --- a/src/format.cc +++ b/src/format.cc @@ -16,6 +16,7 @@ template FMT_API auto dragonbox::to_decimal(double x) noexcept -> dragonbox::decimal_fp; #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() const -> std::locale; #endif diff --git a/src/os.cc b/src/os.cc index 102000be..c833a051 100644 --- a/src/os.cc +++ b/src/os.cc @@ -160,7 +160,7 @@ void detail::format_windows_error(detail::buffer& out, int error_code, } void report_windows_error(int error_code, const char* message) noexcept { - report_error(detail::format_windows_error, error_code, message); + do_report_error(detail::format_windows_error, error_code, message); } #endif // _WIN32 diff --git a/support/manage.py b/support/release.py similarity index 69% rename from support/manage.py rename to support/release.py index 3467afb0..26de7f4f 100755 --- a/support/manage.py +++ b/support/release.py @@ -1,10 +1,9 @@ #!/usr/bin/env python3 -"""Manage site and releases. +"""Make a release. Usage: - manage.py release [] - manage.py site + release.py [] For the release command $FMT_TOKEN should contain a GitHub personal access token obtained from https://github.com/settings/tokens. @@ -12,9 +11,9 @@ obtained from https://github.com/settings/tokens. from __future__ import print_function import datetime, docopt, errno, fileinput, json, os -import re, requests, shutil, sys -from contextlib import contextmanager +import re, shutil, sys from subprocess import check_call +import urllib.request class Git: @@ -81,46 +80,15 @@ def create_build_env(): return env -fmt_repo_url = 'git@github.com:fmtlib/fmt' - - -def update_site(env): - env.fmt_repo.update(fmt_repo_url) - - doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev')) - doc_repo.update('git@github.com:fmtlib/fmt.dev') - - version = '11.0.0' - clean_checkout(env.fmt_repo, version) - target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc') - - # Build the docs. - html_dir = os.path.join(env.build_dir, 'html') - if os.path.exists(html_dir): - shutil.rmtree(html_dir) - include_dir = env.fmt_repo.dir - import build - build.build_docs(version, doc_dir=target_doc_dir, - include_dir=include_dir, work_dir=env.build_dir) - shutil.rmtree(os.path.join(html_dir, '.doctrees')) - # Copy docs to the website. - version_doc_dir = os.path.join(doc_repo.dir, version) - try: - shutil.rmtree(version_doc_dir) - except OSError as e: - if e.errno != errno.ENOENT: - raise - shutil.move(html_dir, version_doc_dir) - - -def release(args): +if __name__ == '__main__': + args = docopt.docopt(__doc__) env = create_build_env() fmt_repo = env.fmt_repo branch = args.get('') if branch is None: branch = 'master' - if not fmt_repo.update('-b', branch, fmt_repo_url): + if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'): clean_checkout(fmt_repo, branch) # Update the date in the changelog and extract the version and the first @@ -191,28 +159,30 @@ def release(args): # Create a release on GitHub. fmt_repo.push('origin', 'release') auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} - r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', - headers=auth_headers, - data=json.dumps({'tag_name': version, - 'target_commitish': 'release', - 'body': changes, 'draft': True})) - if r.status_code != 201: - raise Exception('Failed to create a release ' + str(r)) - id = r.json()['id'] + req = urllib.request.Request( + 'https://api.github.com/repos/fmtlib/fmt/releases', + data=json.dumps({'tag_name': version, + 'target_commitish': 'release', + 'body': changes, 'draft': True}).encode('utf-8'), + headers=auth_headers, method='POST') + with urllib.request.urlopen(req) as response: + if response.status != 201: + raise Exception(f'Failed to create a release ' + + '{response.status} {response.reason}') + response_data = json.loads(response.read().decode('utf-8')) + id = response_data['id'] + + # Upload the package. uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' package = 'fmt-{}.zip'.format(version) - r = requests.post( - '{}/{}/assets?name={}'.format(uploads_url, id, package), + req = urllib.request.Request( + f'{uploads_url}/{id}/assets?name={package}', headers={'Content-Type': 'application/zip'} | auth_headers, - data=open('build/fmt/' + package, 'rb')) - if r.status_code != 201: - raise Exception('Failed to upload an asset ' + str(r)) + data=open('build/fmt/' + package, 'rb').read(), method='POST') + with urllib.request.urlopen(req) as response: + if response.status != 201: + raise Exception(f'Failed to upload an asset ' + '{response.status} {response.reason}') - update_site(env) - -if __name__ == '__main__': - args = docopt.docopt(__doc__) - if args.get('release'): - release(args) - elif args.get('site'): - update_site(create_build_env()) + short_version = '.'.join(version.split('.')[:-1]) + check_call(['./mkdocs', 'deploy', short_version]) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index adb6fa6d..e1ea260d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,13 +62,14 @@ if (NOT (MSVC AND BUILD_SHARED_LIBS)) endif () add_fmt_test(ostream-test) add_fmt_test(compile-test) -add_fmt_test(compile-fp-test HEADER_ONLY) +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) add_fmt_test(scan-test HEADER_ONLY) check_symbol_exists(strptime "time.h" HAVE_STRPTIME) diff --git a/test/args-test.cc b/test/args-test.cc index f99ff74e..e3efa244 100644 --- a/test/args-test.cc +++ b/test/args-test.cc @@ -186,3 +186,17 @@ TEST(args_test, move_constructor) { store.reset(); EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo"); } + +TEST(args_test, size) { + fmt::dynamic_format_arg_store store; + EXPECT_EQ(store.size(), 0); + + store.push_back(42); + EXPECT_EQ(store.size(), 1); + + store.push_back("Molybdenum"); + EXPECT_EQ(store.size(), 2); + + store.clear(); + EXPECT_EQ(store.size(), 0); +} diff --git a/test/base-test.cc b/test/base-test.cc index cc0901a4..52089fea 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -645,9 +645,7 @@ TEST(base_test, is_formattable) { EXPECT_TRUE(fmt::is_formattable::value); EXPECT_TRUE(fmt::is_formattable::value); -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910 EXPECT_FALSE(fmt::is_formattable::value); -#endif EXPECT_FALSE(fmt::is_formattable::value); const auto f = convertible_to_pointer_formattable(); @@ -745,19 +743,6 @@ TEST(base_test, no_implicit_conversion_to_string_view) { fmt::is_formattable::value); } -#ifdef FMT_USE_STRING_VIEW -struct implicitly_convertible_to_std_string_view { - operator std::string_view() const { return "foo"; } -}; - -TEST(base_test, no_implicit_conversion_to_std_string_view) { - EXPECT_FALSE( - fmt::is_formattable::value); -} -#endif - -// std::is_constructible is broken in MSVC until version 2015. -#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900 struct explicitly_convertible_to_string_view { explicit operator fmt::string_view() const { return "foo"; } }; @@ -769,7 +754,16 @@ TEST(base_test, format_explicitly_convertible_to_string_view) { !fmt::is_formattable::value, ""); } -# ifdef FMT_USE_STRING_VIEW +#if FMT_CPLUSPLUS >= 201703L +struct implicitly_convertible_to_std_string_view { + operator std::string_view() const { return "foo"; } +}; + +TEST(base_test, no_implicit_conversion_to_std_string_view) { + EXPECT_FALSE( + fmt::is_formattable::value); +} + struct explicitly_convertible_to_std_string_view { explicit operator std::string_view() const { return "foo"; } }; @@ -781,8 +775,7 @@ TEST(base_test, format_explicitly_convertible_to_std_string_view) { !fmt::is_formattable::value, ""); } -# endif -#endif +#endif // FMT_CPLUSPLUS >= 201703L TEST(base_test, has_formatter) { EXPECT_TRUE((fmt::detail::has_formatter())); @@ -868,3 +861,17 @@ TEST(base_test, format_to_custom_container) { auto c = custom_container(); fmt::format_to(std::back_inserter(c), ""); } + +struct nondeterministic_format_string { + mutable int i = 0; + FMT_CONSTEXPR operator string_view() const { + return string_view("{}", i++ != 0 ? 2 : 0); + } +}; + +TEST(base_test, no_repeated_format_string_conversions) { +#if !FMT_GCC_VERSION + char buf[10]; + fmt::format_to(buf, nondeterministic_format_string()); +#endif +} diff --git a/test/compile-test.cc b/test/compile-test.cc index 9e7d3c6d..043588e4 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -8,6 +8,7 @@ #include "fmt/compile.h" #include +#include #include "fmt/chrono.h" #include "fmt/ranges.h" @@ -229,10 +230,14 @@ TEST(compile_test, unknown_format_fallback) { EXPECT_EQ(" 42 ", fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42))); - std::vector v; - fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"), + std::vector v1; + fmt::format_to(std::back_inserter(v1), FMT_COMPILE("{}"), 42); + EXPECT_EQ("42", fmt::string_view(v1.data(), v1.size())); + + std::vector v2; + fmt::format_to(std::back_inserter(v2), FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)); - EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size())); + EXPECT_EQ(" 42 ", fmt::string_view(v2.data(), v2.size())); char buffer[4]; auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"), @@ -265,11 +270,23 @@ TEST(compile_test, to_string_and_formatter) { fmt::format(FMT_COMPILE("{}"), to_stringable()); } +struct std_context_test {}; + +FMT_BEGIN_NAMESPACE +template <> struct formatter : formatter { + auto format(std_context_test, format_context& ctx) const + -> decltype(ctx.out()) { + return ctx.out(); + } +}; +FMT_END_NAMESPACE + TEST(compile_test, print) { EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"), "Don't panic!"); EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"), "Don't panic!"); + fmt::print(FMT_COMPILE("{}"), std_context_test()); } #endif diff --git a/test/format-impl-test.cc b/test/format-impl-test.cc index 9645f832..1f2ef703 100644 --- a/test/format-impl-test.cc +++ b/test/format-impl-test.cc @@ -283,7 +283,7 @@ struct double_double { double a; double b; - explicit constexpr double_double(double a_val = 0, double b_val = 0) + constexpr explicit double_double(double a_val = 0, double b_val = 0) : a(a_val), b(b_val) {} operator double() const { return a + b; } @@ -299,7 +299,7 @@ bool operator>=(const double_double& lhs, const double_double& rhs) { struct slow_float { float value; - explicit constexpr slow_float(float val = 0) : value(val) {} + constexpr explicit slow_float(float val = 0) : value(val) {} operator float() const { return value; } auto operator-() const -> slow_float { return slow_float(-value); } }; diff --git a/test/format-test.cc b/test/format-test.cc index 1c907c87..60707e6a 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -1821,7 +1821,9 @@ TEST(format_test, big_print) { } // Windows CRT implements _IOLBF incorrectly (full buffering). -#if FMT_USE_FCNTL && !defined(_WIN32) +#if FMT_USE_FCNTL + +# ifndef _WIN32 TEST(format_test, line_buffering) { auto pipe = fmt::pipe(); @@ -1845,7 +1847,26 @@ TEST(format_test, line_buffering) { reader.join(); } -#endif +# 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; diff --git a/test/no-builtin-types-test.cc b/test/no-builtin-types-test.cc new file mode 100644 index 00000000..fd15d002 --- /dev/null +++ b/test/no-builtin-types-test.cc @@ -0,0 +1,24 @@ +// 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 "gtest/gtest.h" + +#if !defined(__GNUC__) || __GNUC__ >= 5 +#define FMT_BUILTIN_TYPES 0 +#include "fmt/format.h" + +TEST(no_builtin_types_test, format) { + EXPECT_EQ(fmt::format("{}", 42), "42"); +} + +TEST(no_builtin_types_test, double_is_custom_type) { + double d = 42; + auto args = fmt::make_format_args(d); + EXPECT_EQ(fmt::format_args(args).get(0).type(), + fmt::detail::type::custom_type); +} +#endif diff --git a/test/ranges-test.cc b/test/ranges-test.cc index a3b659db..2e5b73c9 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -28,11 +28,6 @@ # define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY #endif -#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1910 -# define FMT_RANGES_TEST_ENABLE_JOIN -# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT -#endif - #ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY TEST(ranges_test, format_array) { int arr[] = {1, 2, 3, 5, 7, 11}; @@ -213,7 +208,6 @@ TEST(ranges_test, tuple_parse_calls_element_parse) { EXPECT_THROW(f.parse(ctx), bad_format); } -#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT struct tuple_like { int i; std::string str; @@ -246,7 +240,6 @@ TEST(ranges_test, format_struct) { auto t = tuple_like{42, "foo"}; EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")"); } -#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT TEST(ranges_test, format_to) { char buf[10]; @@ -401,7 +394,6 @@ TEST(ranges_test, join_bytes) { } #endif -#ifdef FMT_RANGES_TEST_ENABLE_JOIN TEST(ranges_test, join_tuple) { // Value tuple args. auto t1 = std::tuple('a', 1, 2.0f); @@ -420,6 +412,10 @@ TEST(ranges_test, join_tuple) { auto t4 = std::tuple(4.0f); EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); + // Tuple-like. + auto t5 = tuple_like{42, "foo"}; + EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo"); + # if FMT_TUPLE_JOIN_SPECIFIERS // Specs applied to each element. auto t5 = std::tuple(-3, 100, 1); @@ -533,8 +529,6 @@ TEST(ranges_test, format_join_adl_begin_end) { EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43"); } -#endif // FMT_RANGES_TEST_ENABLE_JOIN - #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L TEST(ranges_test, nested_ranges) { auto l = std::list{1, 2, 3}; diff --git a/test/scan.h b/test/scan.h index f6aaf4e3..8a58ea7b 100644 --- a/test/scan.h +++ b/test/scan.h @@ -211,7 +211,7 @@ class scan_parse_context { public: using iterator = string_view::iterator; - explicit FMT_CONSTEXPR scan_parse_context(string_view format) + FMT_CONSTEXPR explicit scan_parse_context(string_view format) : format_(format) {} FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); } @@ -347,7 +347,7 @@ class scan_context { using iterator = detail::scan_iterator; using sentinel = detail::scan_sentinel; - explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args) + FMT_CONSTEXPR explicit scan_context(detail::scan_buffer& buf, scan_args args) : buf_(buf), args_(args) {} FMT_CONSTEXPR auto arg(int id) const -> scan_arg { diff --git a/test/std-test.cc b/test/std-test.cc index ab458ae8..2c57b3f6 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -91,6 +91,9 @@ TEST(std_test, complex) { EXPECT_EQ(fmt::format("{: }", std::complex(1, 2.2)), "( 1+2.2i)"); EXPECT_EQ(fmt::format("{: }", std::complex(1, -2.2)), "( 1-2.2i)"); + EXPECT_EQ(fmt::format("{:8}", std::complex(1, 2)), "(1+2i) "); + EXPECT_EQ(fmt::format("{:-<8}", std::complex(1, 2)), "(1+2i)--"); + EXPECT_EQ(fmt::format("{:>20.2f}", std::complex(1, 2.2)), " (1.00+2.20i)"); EXPECT_EQ(fmt::format("{:<20.2f}", std::complex(1, 2.2)), diff --git a/test/test-assert.h b/test/test-assert.h index ab7b2bf7..bec3dfc3 100644 --- a/test/test-assert.h +++ b/test/test-assert.h @@ -12,7 +12,7 @@ void throw_assertion_failure(const char* message); #define FMT_ASSERT(condition, message) \ - if (!(condition)) throw_assertion_failure(message); + ((condition) ? (void)0 : throw_assertion_failure(message)) #include "gtest/gtest.h" diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 215b659f..fd613e82 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -72,16 +72,17 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) { #endif TEST(xchar_test, format) { - EXPECT_EQ(L"42", fmt::format(L"{}", 42)); - EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); - EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); - EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); + EXPECT_EQ(fmt::format(L"{}", 42), L"42"); + EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2"); + 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); - EXPECT_EQ(L"true", fmt::format(L"{}", true)); - EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); - EXPECT_EQ(L"Cyrillic letter \x42e", - fmt::format(L"Cyrillic letter {}", L'\x42e')); - EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); + EXPECT_EQ(fmt::format(L"{}", true), L"true"); + EXPECT_EQ(fmt::format(L"{0}", L'a'), L"a"); + EXPECT_EQ(fmt::format(L"Letter {}", L'\x40e'), L"Letter \x40e"); // Ў + if (sizeof(wchar_t) == 4) + EXPECT_EQ(fmt::format(fmt::runtime(L"{:𓀨>3}"), 42), L"𓀨42"); + EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), L"abc1"); } TEST(xchar_test, is_formattable) { @@ -490,12 +491,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(1, 2)); EXPECT_EQ(s, L"(1+2i)"); EXPECT_EQ(fmt::format(L"{:.2f}", std::complex(1, 2)), L"(1.00+2.00i)"); EXPECT_EQ(fmt::format(L"{:8}", std::complex(1, 2)), L"(1+2i) "); + EXPECT_EQ(fmt::format(L"{:-<8}", std::complex(1, 2)), L"(1+2i)--"); } TEST(std_test_xchar, optional) {