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) {