diff --git a/.clang-format b/.clang-format index 31f8c343..08fa7ba8 100644 --- a/.clang-format +++ b/.clang-format @@ -7,6 +7,7 @@ IndentCaseLabels: false AlwaysBreakTemplateDeclarations: false DerivePointerAlignment: false AllowShortCaseLabelsOnASingleLine: true +QualifierAlignment: Left AlignConsecutiveShortCaseStatements: Enabled: true AcrossEmptyLines: true diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index 50182ee9..3769e2a8 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -25,7 +25,7 @@ jobs: language: c++ - name: Upload crash - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 if: failure() && steps.build.outcome == 'success' with: name: artifacts diff --git a/.github/workflows/doc.yml b/.github/workflows/doc.yml index 019a85f1..d76eb47e 100644 --- a/.github/workflows/doc.yml +++ b/.github/workflows/doc.yml @@ -7,8 +7,7 @@ permissions: jobs: build: - # Use Ubuntu 20.04 because doxygen 1.8.13 from Ubuntu 18.04 is broken. - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 @@ -25,7 +24,7 @@ jobs: run: | sudo apt update sudo apt install doxygen - pip install mkdocs-material==9.5.25 mkdocstrings==0.25.1 mike==2.1.1 + pip install mkdocs-material==9.5.25 mkdocstrings==0.26.1 mike==2.1.1 cmake -E make_directory ${{runner.workspace}}/build # Workaround https://github.com/actions/checkout/issues/13: git config --global user.name "$(git --no-pager log --format=format:'%an' -n 1)" diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 97150467..42a2d2ce 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -7,87 +7,143 @@ permissions: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: matrix: - cxx: [g++-4.9, g++-10, clang++-9] + cxx: [g++-4.9, g++-11, clang++-3.6, clang++-11] build_type: [Debug, Release] std: [11] shared: [""] include: - cxx: g++-4.9 - install: sudo apt install g++-4.9 - - cxx: g++-8 + - cxx: clang++-3.6 + - cxx: g++-11 build_type: Debug std: 14 - install: sudo apt install g++-8 - - cxx: g++-8 - build_type: Debug - std: 17 - install: sudo apt install g++-8 - - cxx: g++-9 - build_type: Debug - std: 17 - - cxx: g++-10 + install: sudo apt install g++-11 + - cxx: g++-11 build_type: Debug std: 17 - cxx: g++-11 build_type: Debug std: 20 install: sudo apt install g++-11 - - cxx: clang++-8 - build_type: Debug - std: 17 - cxxflags: -stdlib=libc++ - install: sudo apt install clang-8 libc++-8-dev libc++abi-8-dev - - cxx: clang++-9 - install: sudo apt install clang-9 - - cxx: clang++-9 - build_type: Debug - fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON - std: 17 - install: sudo apt install clang-9 - - cxx: clang++-11 - build_type: Debug - std: 20 - - cxx: clang++-11 - build_type: Debug - std: 20 - cxxflags: -stdlib=libc++ - install: sudo apt install libc++-11-dev libc++abi-11-dev - cxx: g++-13 build_type: Release std: 23 install: sudo apt install g++-13 shared: -DBUILD_SHARED_LIBS=ON + - cxx: clang++-11 + build_type: Debug + std: 17 + cxxflags: -stdlib=libc++ + install: sudo apt install clang-11 libc++-11-dev libc++abi-11-dev + - cxx: clang++-11 + install: sudo apt install clang-11 + - cxx: clang++-11 + build_type: Debug + fuzz: -DFMT_FUZZ=ON -DFMT_FUZZ_LINKMAIN=ON + std: 17 + install: sudo apt install clang-11 + - cxx: clang++-14 + build_type: Debug + std: 20 + - cxx: clang++-14 + build_type: Debug + std: 20 + cxxflags: -stdlib=libc++ + install: sudo apt install libc++-14-dev libc++abi-14-dev steps: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set timezone - run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' + run: sudo timedatectl set-timezone 'Europe/Kyiv' - - name: Add repositories for older GCC + - name: Install GCC 4.9 run: | - # Below repo provides GCC 4.9. - sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial main' - sudo apt-add-repository 'deb http://dk.archive.ubuntu.com/ubuntu/ xenial universe' + sudo apt update + sudo apt install libatomic1 libc6-dev libgomp1 libitm1 libmpc3 + # https://launchpad.net/ubuntu/xenial/amd64/g++-4.9/4.9.3-13ubuntu2 + wget --no-verbose \ + http://launchpadlibrarian.net/230069137/libmpfr4_3.1.3-2_amd64.deb \ + http://launchpadlibrarian.net/253728424/libasan1_4.9.3-13ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/253728426/libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/253728432/libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/253728314/gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/253728399/cpp-4.9_4.9.3-13ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/253728404/gcc-4.9_4.9.3-13ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/253728401/g++-4.9_4.9.3-13ubuntu2_amd64.deb + sudo dpkg -i \ + libmpfr4_3.1.3-2_amd64.deb \ + libasan1_4.9.3-13ubuntu2_amd64.deb \ + libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libgcc-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ + libstdc++-4.9-dev_4.9.3-13ubuntu2_amd64.deb \ + gcc-4.9-base_4.9.3-13ubuntu2_amd64.deb \ + gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + cpp-4.9_4.9.3-13ubuntu2_amd64.deb \ + gcc-4.9_4.9.3-13ubuntu2_amd64.deb \ + g++-4.9_4.9.3-13ubuntu2_amd64.deb if: ${{ matrix.cxx == 'g++-4.9' }} + - name: Install Clang 3.6 + run: | + sudo apt update + sudo apt install libtinfo5 + # https://code.launchpad.net/ubuntu/xenial/amd64/clang-3.6/1:3.6.2-3ubuntu2 + wget --no-verbose \ + http://launchpadlibrarian.net/230019046/libffi6_3.2.1-4_amd64.deb \ + http://launchpadlibrarian.net/445346109/libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346135/libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346112/libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346128/libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346113/libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346131/libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/445346022/libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/254405108/libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/254405097/libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/254405101/libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \ + http://launchpadlibrarian.net/445345919/gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + http://launchpadlibrarian.net/254405091/clang-3.6_3.6.2-3ubuntu2_amd64.deb + sudo dpkg -i \ + libffi6_3.2.1-4_amd64.deb \ + libasan2_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libubsan0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libcilkrts5_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libmpx0_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libgcc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libstdc++-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libobjc-5-dev_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + libllvm3.6v5_3.6.2-3ubuntu2_amd64.deb \ + libclang-common-3.6-dev_3.6.2-3ubuntu2_amd64.deb \ + libclang1-3.6_3.6.2-3ubuntu2_amd64.deb \ + gcc-5-base_5.4.0-6ubuntu1~16.04.12_amd64.deb \ + clang-3.6_3.6.2-3ubuntu2_amd64.deb + if: ${{ matrix.cxx == 'clang++-3.6' }} + - name: Add repositories for newer GCC run: | sudo apt-add-repository ppa:ubuntu-toolchain-r/test - if: ${{ matrix.cxx == 'g++-11' || matrix.cxx == 'g++-13' }} + if: ${{ matrix.cxx == 'g++-13' }} - name: Add Ubuntu mirrors run: | - # Github Actions caching proxy is at times unreliable - # see https://github.com/actions/runner-images/issues/7048 - printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | sudo tee /etc/apt/mirrors.txt - curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append /etc/apt/mirrors.txt - sudo sed -i 's~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:/etc/apt/mirrors.txt~' /etc/apt/sources.list + # GitHub Actions caching proxy is at times unreliable + # see https://github.com/actions/runner-images/issues/7048. + mirrors=/etc/apt/mirrors.txt + printf 'http://azure.archive.ubuntu.com/ubuntu\tpriority:1\n' | \ + sudo tee $mirrors + curl http://mirrors.ubuntu.com/mirrors.txt | sudo tee --append $mirrors + sudo sed -i \ + "s~http://azure.archive.ubuntu.com/ubuntu/~mirror+file:$mirrors~" \ + /etc/apt/sources.list - - name: Create Build Environment + - name: Create build environment run: | sudo apt update ${{matrix.install}} @@ -100,10 +156,12 @@ jobs: CXX: ${{matrix.cxx}} CXXFLAGS: ${{matrix.cxxflags}} run: | - cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ${{matrix.fuzz}} ${{matrix.shared}} \ - -DCMAKE_CXX_STANDARD=${{matrix.std}} -DFMT_DOC=OFF \ - -DCMAKE_CXX_VISIBILITY_PRESET=hidden -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ - -DFMT_PEDANTIC=ON -DFMT_WERROR=ON $GITHUB_WORKSPACE + cmake -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -DCMAKE_CXX_VISIBILITY_PRESET=hidden \ + -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON \ + -DFMT_DOC=OFF -DFMT_PEDANTIC=ON -DFMT_WERROR=ON \ + ${{matrix.fuzz}} ${{matrix.shared}} $GITHUB_WORKSPACE - name: Build working-directory: ${{runner.workspace}}/build diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 3543ef57..1a297e6d 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -28,7 +28,7 @@ jobs: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set timezone - run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' + run: sudo systemsetup -settimezone 'Europe/Minsk' - name: Select Xcode 14.3 (macOS 13) run: sudo xcode-select -s "/Applications/Xcode_14.3.app" diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index b363a6f8..cb435568 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -34,7 +34,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif @@ -52,7 +52,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 + uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0 with: name: SARIF file path: results.sarif @@ -60,6 +60,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0 + uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 with: sarif_file: results.sarif diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5403e652..931e7589 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0 - name: Set timezone - run: tzutil /s "Ekaterinburg Standard Time" + run: tzutil /s "FLE Standard Time" - name: Create Build Environment run: cmake -E make_directory ${{runner.workspace}}/build @@ -78,7 +78,7 @@ jobs: - name: Set timezone run: tzutil /s "Ekaterinburg Standard Time" shell: cmd - - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.0 + - uses: msys2/setup-msys2@61f9e5e925871ba6c9e3e8da24ede83ea27fa91f # v2.27.0 with: release: false msystem: ${{matrix.sys}} diff --git a/.gitignore b/.gitignore index 2635026a..edd4b086 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.xcodeproj *~ .vscode/ +.vs/ /CMakeScripts /Testing /_CPack_Packages diff --git a/CMakeLists.txt b/CMakeLists.txt index 586ead51..dd0e0743 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,13 @@ endfunction() # DEPRECATED! Should be merged into add_module_library. function(enable_module target) if (MSVC) - set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc) + if(CMAKE_GENERATOR STREQUAL "Ninja") + # Ninja dyndep expects the .ifc output to be located in a specific relative path + file(RELATIVE_PATH BMI_DIR "${CMAKE_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/${target}.dir") + else() + set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}") + endif() + file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI) target_compile_options(${target} PRIVATE /interface /ifcOutput ${BMI} INTERFACE /reference fmt=${BMI}) @@ -69,8 +75,6 @@ function(add_module_library name) target_compile_options(${name} PUBLIC -fmodules-ts) endif () - target_compile_definitions(${name} PRIVATE FMT_MODULE) - if (FMT_USE_CMAKE_MODULES) target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES FILES ${sources}) @@ -201,8 +205,7 @@ if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) endif () -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} - "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake") include(CheckCXXCompilerFlag) include(JoinPaths) @@ -295,6 +298,7 @@ function(add_headers VAR) endfunction() # Define the fmt library, its includes and the needed defines. +set(FMT_HEADERS) add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h format-inl.h os.h ostream.h printf.h ranges.h std.h xchar.h) @@ -427,7 +431,7 @@ if (FMT_INSTALL) # Install the library and headers. install(TARGETS ${INSTALL_TARGETS} - COMPONENT core + COMPONENT fmt_core EXPORT ${targets_export_name} LIBRARY DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR} @@ -443,13 +447,13 @@ if (FMT_INSTALL) # Install version, config and target files. install(FILES ${project_config} ${version_config} DESTINATION ${FMT_CMAKE_DIR} - COMPONENT core) + COMPONENT fmt_core) install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} NAMESPACE fmt:: - COMPONENT core) + COMPONENT fmt_core) install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}" - COMPONENT core) + COMPONENT fmt_core) endif () function(add_doc_target) @@ -486,7 +490,7 @@ function(add_doc_target) include(GNUInstallDirs) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/ DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt - COMPONENT doc OPTIONAL) + COMPONENT fmt_doc OPTIONAL) endfunction() if (FMT_DOC) diff --git a/ChangeLog.md b/ChangeLog.md index 09ebaed6..56baba39 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,3 +1,187 @@ +# 11.2.0 - 2025-05-03 + +- Added the `s` specifier for `std::error_code`. It allows formatting an error + message as a string. For example: + + ```c++ + #include + + int main() { + auto ec = std::make_error_code(std::errc::no_such_file_or_directory); + fmt::print("{:s}\n", ec); + } + ``` + + prints + + ``` + No such file or directory + ``` + (The actual message is platform-specific.) + +- Fixed formatting of `std::chrono::local_time` and `tm` + (https://github.com/fmtlib/fmt/issues/3815, + https://github.com/fmtlib/fmt/issues/4350). + For example ([godbolt](https://www.godbolt.org/z/8o4b1PPn5)): + + ```c++ + #include + + int main() { + std::chrono::zoned_time zt( + std::chrono::current_zone(), + std::chrono::system_clock::now()); + fmt::print("{}", zt.get_local_time()); + } + ``` + + is now formatted consistenly across platforms. + +- Added diagnostics for cases when timezone information is not available. + For example: + + ```c++ + fmt::print("{:Z}", std::chrono::local_seconds()); + ``` + + now gives a compile-time error. + +- Deprecated `fmt::localtime` in favor of `std::localtime`. + +- Fixed compilation with GCC 15 and C++20 modules enabled + (https://github.com/fmtlib/fmt/pull/4347). Thanks @tkhyn. + +- Fixed handling of named arguments in format specs + (https://github.com/fmtlib/fmt/issues/4360, + https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight. + +- Added error reporting for duplicate named arguments + (https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight. + +- Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0` + (https://github.com/fmtlib/fmt/issues/4375, + https://github.com/fmtlib/fmt/issues/4394). + +- Optimized `text_style` using bit packing + (https://github.com/fmtlib/fmt/pull/4363). Thanks @LocalSpook. + +- Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180, + https://github.com/fmtlib/fmt/pull/4383). Thanks @LocalSpook. + +- Fixed a flush issue in `fmt::print` when using libstdc++ + (https://github.com/fmtlib/fmt/issues/4398). + +- Fixed `fmt::println` usage with `FMT_ENFORCE_COMPILE_STRING` and legacy + compile-time checks (https://github.com/fmtlib/fmt/pull/4407). + Thanks @madmaxoft. + +- Removed legacy header `fmt/core.h` from docs + (https://github.com/fmtlib/fmt/pull/4421, + https://github.com/fmtlib/fmt/pull/4422). Thanks @krzysztofkortas. + +- Worked around limitations of `__builtin_strlen` during constant evaluation + (https://github.com/fmtlib/fmt/issues/4423, + https://github.com/fmtlib/fmt/pull/4429). Thanks @BRevzin. + +- Worked around a bug in MSVC v141 (https://github.com/fmtlib/fmt/issues/4412, + https://github.com/fmtlib/fmt/pull/4413). Thanks @hirohira9119. + +- Removed the `fmt_detail` namespace + (https://github.com/fmtlib/fmt/issues/4324). + +- Removed specializations of `std::is_floating_point` in tests + (https://github.com/fmtlib/fmt/issues/4417). + +- Fixed a CMake error when setting `CMAKE_MODULE_PATH` in the pedantic mode + (https://github.com/fmtlib/fmt/pull/4426). Thanks @rlalik. + +- Updated the Bazel config (https://github.com/fmtlib/fmt/pull/4400). + Thanks @Vertexwahn. + +# 11.1.4 - 2025-02-26 + +- Fixed ABI compatibility with earlier 11.x versions on Windows + (https://github.com/fmtlib/fmt/issues/4359). + +- Improved the logic of switching between fixed and exponential format for + `float` (https://github.com/fmtlib/fmt/issues/3649). + +- Moved `is_compiled_string` to the public API + (https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb. + +- Simplified implementation of `operator""_cf` + (https://github.com/fmtlib/fmt/pull/4349). Thanks @LocalSpook. + +- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329). + Thanks @LocalSpook. + +- Fixed handling of BMI paths with the Ninja generator + (https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn. + +- Fixed gcc 8.3 compile errors (https://github.com/fmtlib/fmt/issues/4331, + https://github.com/fmtlib/fmt/pull/4336). Thanks @sergiud. + +- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/pull/4356). + Thanks @dinomight. + +# 11.1.3 - 2025-01-25 + +- Fixed compilation on GCC 9.4 (https://github.com/fmtlib/fmt/issues/4313). + +- Worked around an internal compiler error when using C++20 modules with GCC + 14.2 and earlier (https://github.com/fmtlib/fmt/issues/4295). + +- Worked around a bug in GCC 6 (https://github.com/fmtlib/fmt/issues/4318). + +- Fixed an issue caused by instantiating `formatter` + (https://github.com/fmtlib/fmt/issues/4303, + https://github.com/fmtlib/fmt/pull/4325). Thanks @timsong-cpp. + +- Fixed formatting into `std::ostreambuf_iterator` when using format string + compilation (https://github.com/fmtlib/fmt/issues/4309, + https://github.com/fmtlib/fmt/pull/4312). Thanks @phprus. + +- Restored a constraint on the map formatter so that it correctly reports as + unformattable when the element is (https://github.com/fmtlib/fmt/pull/4326). + Thanks @timsong-cpp. + +- Reduced the size of format specs (https://github.com/fmtlib/fmt/issues/4298). + +- Readded `args()` to `fmt::format_context` + (https://github.com/fmtlib/fmt/issues/4307, + https://github.com/fmtlib/fmt/pull/4310). Thanks @Erroneous1. + +- Fixed a bogus MSVC warning (https://github.com/fmtlib/fmt/issues/4314, + https://github.com/fmtlib/fmt/pull/4322). Thanks @ZehMatt. + +- Fixed a pedantic mode error in the CMake config + (https://github.com/fmtlib/fmt/pull/4327). Thanks @rlalik. + +# 11.1.2 - 2025-01-12 + +- Fixed ABI compatibility with earlier 11.x versions + (https://github.com/fmtlib/fmt/issues/4292). + +- Added `wchar_t` support to the `std::bitset` formatter + (https://github.com/fmtlib/fmt/issues/4285, + https://github.com/fmtlib/fmt/pull/4286, + https://github.com/fmtlib/fmt/issues/4289, + https://github.com/fmtlib/fmt/pull/4290). Thanks @phprus. + +- Prefixed CMake components with `fmt-` to simplify usage of {fmt} via + `add_subdirectory` (https://github.com/fmtlib/fmt/issues/4283). + +- Updated docs for meson (https://github.com/fmtlib/fmt/pull/4291). + Thanks @trim21. + +- Fixed a compilation error in chrono on nvcc + (https://github.com/fmtlib/fmt/issues/4297, + https://github.com/fmtlib/fmt/pull/4301). Thanks @breyerml. + +- Fixed various warnings + (https://github.com/fmtlib/fmt/pull/4288, + https://github.com/fmtlib/fmt/pull/4299). Thanks @GamesTrap and @edo9300. + # 11.1.1 - 2024-12-27 - Fixed ABI compatibility with earlier 11.x versions diff --git a/README.md b/README.md index fd845db2..638ec522 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v). hundred million integers to strings per second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html) - Small code size both in terms of source code with the minimum - configuration consisting of just three files, `core.h`, `format.h` + configuration consisting of just three files, `base.h`, `format.h` and `format-inl.h`, and compiled code; see [Compile time and code bloat](#compile-time-and-code-bloat) - Reliability: the library has an extensive set of @@ -74,7 +74,7 @@ See the [documentation](https://fmt.dev) for more details. **Print to stdout** ([run](https://godbolt.org/z/Tevcjh)) ``` c++ -#include +#include int main() { fmt::print("Hello, world!\n"); diff --git a/doc/ChangeLog-old.md b/doc/ChangeLog-old.md index 3f31d1e9..e8f993c4 100644 --- a/doc/ChangeLog-old.md +++ b/doc/ChangeLog-old.md @@ -674,7 +674,7 @@ https://github.com/fmtlib/fmt/issues/1747, https://github.com/fmtlib/fmt/pull/1750). Thanks @gsjaardema, @gabime, @johnor, @Kurkin, @invexed, @peterbell10, - @daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @erthink, + @daixtrose, @petrutlucian94, @Neargye, @ambitslix, @gabime, @tohammer and @0x8000-0000. # 6.2.1 - 2020-05-09 diff --git a/doc/api.md b/doc/api.md index e86f0b06..61e5c3de 100644 --- a/doc/api.md +++ b/doc/api.md @@ -79,6 +79,8 @@ time formatting and [`fmt/std.h`](#std-api) for other standard library types. There are two ways to make a user-defined type formattable: providing a `format_as` function or specializing the `formatter` struct template. +Formatting of non-void pointer types is intentionally disallowed and they +cannot be made formattable via either extension API. Use `format_as` if you want to make your type formattable as some other type with the same format specifiers. The `format_as` function should @@ -220,7 +222,7 @@ You can also write a formatter for a hierarchy of classes: ```c++ // demo.h: #include -#include +#include struct A { virtual ~A() {} @@ -403,7 +405,7 @@ All formatting is locale-independent by default. Use the `'L'` format specifier to insert the appropriate number separator characters from the locale: - #include + #include #include std::locale::global(std::locale("en_US.UTF-8")); @@ -413,11 +415,11 @@ locale: that take `std::locale` as a parameter. The locale type is a template parameter to avoid the expensive `` include. -::: format(detail::locale_ref, format_string, T&&...) +::: format(const Locale&, format_string, T&&...) -::: format_to(OutputIt, detail::locale_ref, format_string, T&&...) +::: format_to(OutputIt, const Locale&, format_string, T&&...) -::: formatted_size(detail::locale_ref, format_string, T&&...) +::: formatted_size(const Locale&, format_string, T&&...) ### Legacy Compile-Time Checks @@ -473,9 +475,9 @@ chrono-format-specifications). #include int main() { - std::time_t t = std::time(nullptr); + auto now = std::chrono::system_clock::now(); - fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); + fmt::print("The date is {:%Y-%m-%d}.\n", now); // Output: The date is 2020-11-07. // (with 2020-11-07 replaced by the current date) @@ -488,8 +490,6 @@ chrono-format-specifications). // Output: strftime-like format: 03:15:30 } -::: localtime(std::time_t) - ::: gmtime(std::time_t) @@ -580,7 +580,7 @@ performance bottleneck. `fmt/color.h` provides support for terminal color and text style output. -::: print(const text_style&, format_string, T&&...) +::: print(text_style, format_string, T&&...) ::: fg(detail::color_type) @@ -669,5 +669,13 @@ following differences: - Names are defined in the `fmt` namespace instead of `std` to avoid collisions with standard library implementations. + - Width calculation doesn't use grapheme clusterization. The latter has been implemented in a separate branch but hasn't been integrated yet. + +- The default floating-point representation in {fmt} uses the smallest + precision that provides round-trip guarantees similarly to other languages + like Java and Python. `std::format` is currently specified in terms of + `std::to_chars` which tries to generate the smallest number of characters + (ignoring redundant digits and sign in exponent) and may procude more + decimal digits than necessary. diff --git a/doc/index.md b/doc/index.md index 4f28e114..a2694736 100644 --- a/doc/index.md +++ b/doc/index.md @@ -122,7 +122,7 @@ 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.10 + C++11 features which are available in GCC 4.9, Clang 3.6, MSVC 19.10 (2017) and later. Newer compiler and standard library features are used if available, and enable additional functionality.

diff --git a/include/fmt/args.h b/include/fmt/args.h index 3ff47880..d8fe241c 100644 --- a/include/fmt/args.h +++ b/include/fmt/args.h @@ -71,7 +71,7 @@ class dynamic_arg_list { * It can be implicitly converted into `fmt::basic_format_args` for passing * into type-erased formatting functions such as `fmt::vformat`. */ -template class dynamic_format_arg_store { +FMT_EXPORT template class dynamic_format_arg_store { private: using char_type = typename Context::char_type; diff --git a/include/fmt/base.h b/include/fmt/base.h index a6948d40..81d8fc03 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 110101 +#define FMT_VERSION 110201 // Detect compiler versions. #if defined(__clang__) && !defined(__ibmxl__) @@ -96,9 +96,9 @@ // Detect C++14 relaxed constexpr. #ifdef FMT_USE_CONSTEXPR // Use the provided definition. -#elif FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L -// GCC only allows throw in constexpr since version 6: -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67371. +#elif FMT_GCC_VERSION >= 702 && FMT_CPLUSPLUS >= 201402L +// GCC only allows constexpr member functions in non-literal types since 7.2: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66297. # define FMT_USE_CONSTEXPR 1 #elif FMT_ICC_VERSION # define FMT_USE_CONSTEXPR 0 // https://github.com/fmtlib/fmt/issues/1628 @@ -209,20 +209,6 @@ # define FMT_DEPRECATED /* deprecated */ #endif -#ifdef FMT_ALWAYS_INLINE -// Use the provided definition. -#elif FMT_GCC_VERSION || FMT_CLANG_VERSION -# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) -#else -# define FMT_ALWAYS_INLINE inline -#endif -// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. -#ifdef NDEBUG -# define FMT_INLINE FMT_ALWAYS_INLINE -#else -# define FMT_INLINE inline -#endif - #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_VISIBILITY(value) __attribute__((visibility(value))) #else @@ -249,6 +235,28 @@ # define FMT_MSC_WARNING(...) #endif +// Enable minimal optimizations for more compact code in debug mode. +FMT_PRAGMA_GCC(push_options) +#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) +FMT_PRAGMA_GCC(optimize("Og")) +# define FMT_GCC_OPTIMIZED +#endif +FMT_PRAGMA_CLANG(diagnostic push) + +#ifdef FMT_ALWAYS_INLINE +// Use the provided definition. +#elif FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE inline +#endif +// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. +#if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED) +# define FMT_INLINE FMT_ALWAYS_INLINE +#else +# define FMT_INLINE inline +#endif + #ifndef FMT_BEGIN_NAMESPACE # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ @@ -294,15 +302,8 @@ #endif #define FMT_APPLY_VARIADIC(expr) \ - using ignore = int[]; \ - (void)ignore { 0, (expr, 0)... } - -// Enable minimal optimizations for more compact code in debug mode. -FMT_PRAGMA_GCC(push_options) -#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) -FMT_PRAGMA_GCC(optimize("Og")) -#endif -FMT_PRAGMA_CLANG(diagnostic push) + using unused = int[]; \ + (void)unused { 0, (expr, 0)... } FMT_BEGIN_NAMESPACE @@ -325,8 +326,8 @@ using underlying_t = typename std::underlying_type::type; template using decay_t = typename std::decay::type; using nullptr_t = decltype(nullptr); -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 -// A workaround for gcc 4.9 to make void_t work in a SFINAE context. +#if (FMT_GCC_VERSION && FMT_GCC_VERSION < 500) || FMT_MSC_VERSION +// A workaround for gcc 4.9 & MSVC v141 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; @@ -466,8 +467,7 @@ template constexpr const char* narrow(const T*) { return nullptr; } constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; } template -FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n) - -> int { +FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int { if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); for (; n != 0; ++s1, ++s2, --n) { if (*s1 < *s2) return -1; @@ -526,20 +526,20 @@ template class basic_string_view { constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} - /// Constructs a string reference object from a C string and a size. + /// Constructs a string view object from a C string and a size. constexpr basic_string_view(const Char* s, size_t count) noexcept : data_(s), size_(count) {} constexpr basic_string_view(nullptr_t) = delete; - /// Constructs a string reference object from a C string. + /// Constructs a string view object from a C string. #if FMT_GCC_VERSION FMT_ALWAYS_INLINE #endif FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { -#if FMT_HAS_BUILTIN(__buitin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION - if (std::is_same::value) { - size_ = __builtin_strlen(detail::narrow(s)); +#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION + if (std::is_same::value && !detail::is_constant_evaluated()) { + size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr. return; } #endif @@ -548,7 +548,7 @@ template class basic_string_view { size_ = len; } - /// Constructs a string reference from a `std::basic_string` or a + /// Constructs a string view from a `std::basic_string` or a /// `std::basic_string_view` object. template ::value&& std::is_same< @@ -585,7 +585,6 @@ template class basic_string_view { return starts_with(basic_string_view(s)); } - // Lexicographically compare this string reference to other. FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { int result = detail::compare(data_, other.data_, min_of(size_, other.size_)); @@ -616,7 +615,7 @@ template class basic_string_view { using string_view = basic_string_view; -/// Specifies if `T` is an extended character type. Can be specialized by users. +// DEPRECATED! Will be merged with is_char and moved to detail. template struct is_xchar : std::false_type {}; template <> struct is_xchar : std::true_type {}; template <> struct is_xchar : std::true_type {}; @@ -625,7 +624,7 @@ template <> struct is_xchar : std::true_type {}; template <> struct is_xchar : std::true_type {}; #endif -// DEPRECATED! Will be replaced with an alias to prevent specializations. +// Specifies if `T` is a character (code unit) type. template struct is_char : is_xchar {}; template <> struct is_char : std::true_type {}; @@ -739,13 +738,15 @@ class basic_specs { max_fill_size = 4 }; - size_t data_ = 1 << fill_size_shift; + unsigned data_ = 1 << fill_size_shift; + static_assert(sizeof(basic_specs::data_) * CHAR_BIT >= 18, ""); // Character (code unit) type is erased to prevent template bloat. char fill_data_[max_fill_size] = {' '}; FMT_CONSTEXPR void set_fill_size(size_t size) { - data_ = (data_ & ~fill_size_mask) | (size << fill_size_shift); + data_ = (data_ & ~fill_size_mask) | + (static_cast(size) << fill_size_shift); } public: @@ -843,7 +844,7 @@ class basic_specs { fill_data_[i & 3] = static_cast(s[i]); } - FMT_CONSTEXPR void set_fill(const basic_specs& specs) { + FMT_CONSTEXPR void copy_fill_from(const basic_specs& specs) { set_fill_size(specs.fill_size()); for (size_t i = 0; i < max_fill_size; ++i) fill_data_[i] = specs.fill_data_[i]; @@ -1030,6 +1031,11 @@ enum { struct view {}; +template +struct is_view : std::false_type {}; +template +struct is_view> : std::is_base_of {}; + template struct named_arg; template struct is_named_arg : std::false_type {}; template struct is_static_named_arg : std::false_type {}; @@ -1062,6 +1068,16 @@ template struct named_arg_info { int id; }; +// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13. +template +FMT_CONSTEXPR void check_for_duplicate(named_arg_info* named_args, + int named_arg_index, + basic_string_view arg_name) { + for (int i = 0; i < named_arg_index; ++i) { + if (named_args[i].name == arg_name) report_error("duplicate named arg"); + } +} + template ::value)> void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { ++arg_index; @@ -1069,6 +1085,7 @@ void init_named_arg(named_arg_info*, int& arg_index, int&, const T&) { template ::value)> void init_named_arg(named_arg_info* named_args, int& arg_index, int& named_arg_index, const T& arg) { + check_for_duplicate(named_args, named_arg_index, arg.name); named_args[named_arg_index++] = {arg.name, arg_index++}; } @@ -1082,12 +1099,13 @@ template ::value)> FMT_CONSTEXPR void init_static_named_arg(named_arg_info* named_args, int& arg_index, int& named_arg_index) { + check_for_duplicate(named_args, named_arg_index, T::name); named_args[named_arg_index++] = {T::name, arg_index++}; } // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. -enum { long_short = sizeof(long) == sizeof(int) }; +enum { long_short = sizeof(long) == sizeof(int) && FMT_BUILTIN_TYPES }; using long_type = conditional_t; using ulong_type = conditional_t; @@ -1119,7 +1137,7 @@ using use_formatter = bool_constant<(std::is_class::value || std::is_enum::value || std::is_union::value || std::is_array::value) && !has_to_string_view::value && !is_named_arg::value && - !use_format_as::value && !use_format_as_member::value>; + !use_format_as::value && !use_format_as_member::value>; template > auto has_formatter_impl(T* p, buffered_context* ctx = nullptr) @@ -1660,12 +1678,12 @@ template struct arg_pack {}; template class format_string_checker { private: - type types_[max_of(1, NUM_ARGS)]; - named_arg_info named_args_[max_of(1, NUM_NAMED_ARGS)]; + 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_[max_of(1, NUM_ARGS)]; + parse_func parse_funcs_[max_of(1, NUM_ARGS)]; public: template @@ -1704,7 +1722,17 @@ class format_string_checker { -> const Char* { context_.advance_to(begin); if (id >= 0 && id < NUM_ARGS) return parse_funcs_[id](context_); - while (begin != end && *begin != '}') ++begin; + + // If id is out of range, it means we do not know the type and cannot parse + // the format at compile time. Instead, skip over content until we finish + // the format spec, accounting for any nested replacements. + for (int bracket_count = 0; + begin != end && (bracket_count > 0 || *begin != '}'); ++begin) { + if (*begin == '{') + ++bracket_count; + else if (*begin == '}') + --bracket_count; + } return begin; } @@ -2004,6 +2032,17 @@ struct has_back_insert_iterator_container_append< .append(std::declval(), std::declval()))>> : std::true_type {}; +template +struct has_back_insert_iterator_container_insert_at_end : std::false_type {}; + +template +struct has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt, + void_t()) + .insert(get_container(std::declval()).end(), + std::declval(), + std::declval()))>> : std::true_type {}; + // An optimized version of std::copy with the output value type (T). template ::value&& @@ -2018,6 +2057,8 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) template ::value && !has_back_insert_iterator_container_append< + OutputIt, InputIt>::value && + has_back_insert_iterator_container_insert_at_end< OutputIt, InputIt>::value)> FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { @@ -2027,7 +2068,11 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) } template ::value)> + FMT_ENABLE_IF(!(is_back_insert_iterator::value && + (has_back_insert_iterator_container_append< + OutputIt, InputIt>::value || + has_back_insert_iterator_container_insert_at_end< + OutputIt, InputIt>::value)))> FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { while (begin != end) *out++ = static_cast(*begin++); return out; @@ -2234,6 +2279,7 @@ template class value { } // Formats an argument of a custom type, such as a user-defined class. + // DEPRECATED! Formatter template parameter will be removed. template static void format_custom(void* arg, parse_context& parse_ctx, Context& ctx) { @@ -2261,28 +2307,27 @@ template <> struct is_output_iterator : std::true_type {}; template struct is_output_iterator< It, T, - void_t&>()++ = std::declval())>> - : std::true_type {}; + enable_if_t&>()++), + T>::value>> : std::true_type {}; #ifndef FMT_USE_LOCALE # define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1) #endif // A type-erased reference to an std::locale to avoid a heavy include. -struct locale_ref { +class locale_ref { #if FMT_USE_LOCALE private: const void* locale_; // A type-erased pointer to std::locale. public: constexpr locale_ref() : locale_(nullptr) {} - - template - locale_ref(const Locale& loc); + template locale_ref(const Locale& loc); inline explicit operator bool() const noexcept { return locale_ != nullptr; } #endif // FMT_USE_LOCALE + public: template auto get() const -> Locale; }; @@ -2310,8 +2355,9 @@ template struct named_arg_store { // args_[0].named_args points to named_args to avoid bloating format_args. - arg_t args[1 + NUM_ARGS]; - named_arg_info named_args[NUM_NAMED_ARGS]; + arg_t args[1u + NUM_ARGS]; + named_arg_info + named_args[static_cast(NUM_NAMED_ARGS)]; template FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) @@ -2344,7 +2390,7 @@ struct format_arg_store { // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. using type = conditional_t[max_of(1, NUM_ARGS)], + arg_t[max_of(1, NUM_ARGS)], named_arg_store>; type args; }; @@ -2656,6 +2702,7 @@ class context { FMT_CONSTEXPR auto arg_id(string_view name) const -> int { return args_.get_id(name); } + auto args() const -> const format_args& { return args_; } // Returns an iterator to the beginning of the output range. FMT_CONSTEXPR auto out() const -> iterator { return out_; } @@ -2701,7 +2748,7 @@ template struct fstring { template FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const char (&s)[N]) : str(s, N - 1) { using namespace detail; - static_assert(count<(std::is_base_of>::value && + static_assert(count<(is_view>::value && std::is_reference::value)...>() == 0, "passing views as lvalues is disallowed"); if (FMT_USE_CONSTEVAL) parse_format_string(s, checker(s, arg_pack())); @@ -2728,9 +2775,9 @@ template struct fstring { std::is_same::value)> FMT_ALWAYS_INLINE fstring(const S&) : str(S()) { FMT_CONSTEXPR auto sv = string_view(S()); - FMT_CONSTEXPR int ignore = + FMT_CONSTEXPR int unused = (parse_format_string(sv, checker(sv, arg_pack())), 0); - detail::ignore_unused(ignore); + detail::ignore_unused(unused); } fstring(runtime_format_string<> fmt) : str(fmt.str) {} diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index abf3671e..6f9a363e 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -22,21 +22,6 @@ #include "format.h" -namespace fmt_detail { -struct time_zone { - template - auto to_sys(T) - -> std::chrono::time_point { - return {}; - } -}; -template inline auto current_zone(T...) -> time_zone* { - return nullptr; -} - -template inline void _tzset(T...) {} -} // namespace fmt_detail - FMT_BEGIN_NAMESPACE // Enable safe chrono durations, unless explicitly disabled. @@ -261,7 +246,7 @@ namespace detail { using utc_clock = std::chrono::utc_clock; #else struct utc_clock { - void to_sys(); + template void to_sys(T); }; #endif @@ -341,7 +326,7 @@ inline auto get_classic_locale() -> const std::locale& { } template struct codecvt_result { - static constexpr const size_t max_size = 32; + static constexpr size_t max_size = 32; CodeUnit buf[max_size]; CodeUnit* end; }; @@ -364,7 +349,7 @@ void write_codecvt(codecvt_result& out, string_view in, template auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc) -> OutputIt { - if (detail::use_utf8 && loc != get_classic_locale()) { + if (const_check(detail::use_utf8) && loc != get_classic_locale()) { // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and // gcc-4. #if FMT_MSC_VERSION != 0 || \ @@ -435,14 +420,11 @@ auto write(OutputIt out, const std::tm& time, const std::locale& loc, return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc); } -template -struct is_same_arithmetic_type - : public std::integral_constant::value && - std::is_integral::value) || - (std::is_floating_point::value && - std::is_floating_point::value)> { -}; +template +using is_similar_arithmetic_type = + bool_constant<(std::is_integral::value && std::is_integral::value) || + (std::is_floating_point::value && + std::is_floating_point::value)>; FMT_NORETURN inline void throw_duration_error() { FMT_THROW(format_error("cannot format duration")); @@ -501,9 +483,9 @@ auto duration_cast(std::chrono::duration from) -> To { #endif } -template < - typename To, typename FromRep, typename FromPeriod, - FMT_ENABLE_IF(!is_same_arithmetic_type::value)> +template ::value)> auto duration_cast(std::chrono::duration from) -> To { // Mixed integer <-> float cast is not supported by safe_duration_cast. return std::chrono::duration_cast(from); @@ -519,12 +501,30 @@ auto to_time_t(sys_time time_point) -> std::time_t { .count(); } -// Workaround a bug in libstdc++ which sets __cpp_lib_chrono to 201907 without -// providing current_zone(): https://github.com/fmtlib/fmt/issues/4160. -template FMT_CONSTEXPR auto has_current_zone() -> bool { - using namespace std::chrono; - using namespace fmt_detail; - return !std::is_same::value; +namespace tz { + +// DEPRECATED! +struct time_zone { + template + auto to_sys(LocalTime) -> sys_time { + return {}; + } +}; +template auto current_zone(T...) -> time_zone* { + return nullptr; +} + +template void _tzset(T...) {} +} // namespace tz + +// DEPRECATED! +inline void tzset_once() { + static bool init = []() { + using namespace tz; + _tzset(); + return false; + }(); + ignore_unused(init); } } // namespace detail @@ -535,7 +535,7 @@ FMT_BEGIN_EXPORT * expressed in local time. Unlike `std::localtime`, this function is * thread-safe on most platforms. */ -inline auto localtime(std::time_t time) -> std::tm { +FMT_DEPRECATED inline auto localtime(std::time_t time) -> std::tm { struct dispatcher { std::time_t time_; std::tm tm_; @@ -572,11 +572,11 @@ inline auto localtime(std::time_t time) -> std::tm { } #if FMT_USE_LOCAL_TIME -template ())> -inline auto localtime(std::chrono::local_time time) -> std::tm { +template +FMT_DEPRECATED auto localtime(std::chrono::local_time time) + -> std::tm { using namespace std::chrono; - using namespace fmt_detail; + using namespace detail::tz; return localtime(detail::to_time_t(current_zone()->to_sys(time))); } #endif @@ -652,7 +652,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b, // Add ASCII '0' to each digit byte and insert separators. digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); - constexpr const size_t len = 8; + constexpr size_t len = 8; if (const_check(is_big_endian())) { char tmp[len]; std::memcpy(tmp, &digits, len); @@ -911,7 +911,14 @@ template struct null_chrono_spec_handler { FMT_CONSTEXPR void on_tz_name() { unsupported(); } }; -struct tm_format_checker : null_chrono_spec_handler { +class tm_format_checker : public null_chrono_spec_handler { + private: + bool has_timezone_ = false; + + public: + constexpr explicit tm_format_checker(bool has_timezone) + : has_timezone_(has_timezone) {} + FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no format")); } @@ -949,8 +956,12 @@ struct tm_format_checker : null_chrono_spec_handler { FMT_CONSTEXPR void on_24_hour_time() {} FMT_CONSTEXPR void on_iso_time() {} FMT_CONSTEXPR void on_am_pm() {} - FMT_CONSTEXPR void on_utc_offset(numeric_system) {} - FMT_CONSTEXPR void on_tz_name() {} + FMT_CONSTEXPR void on_utc_offset(numeric_system) { + if (!has_timezone_) FMT_THROW(format_error("no timezone")); + } + FMT_CONSTEXPR void on_tz_name() { + if (!has_timezone_) FMT_THROW(format_error("no timezone")); + } }; inline auto tm_wday_full_name(int wday) -> const char* { @@ -980,24 +991,27 @@ inline auto tm_mon_short_name(int mon) -> const char* { } template -struct has_member_data_tm_gmtoff : std::false_type {}; +struct has_tm_gmtoff : std::false_type {}; template -struct has_member_data_tm_gmtoff> - : std::true_type {}; +struct has_tm_gmtoff> : std::true_type {}; -template -struct has_member_data_tm_zone : std::false_type {}; +template struct has_tm_zone : std::false_type {}; template -struct has_member_data_tm_zone> - : std::true_type {}; +struct has_tm_zone> : std::true_type {}; -inline void tzset_once() { - static bool init = []() { - using namespace fmt_detail; - _tzset(); - return false; - }(); - ignore_unused(init); +template ::value)> +bool set_tm_zone(T& time, char* tz) { + time.tm_zone = tz; + return true; +} +template ::value)> +bool set_tm_zone(T&, char*) { + return false; +} + +inline char* utc() { + static char tz[] = "UTC"; + return tz; } // Converts value to Int and checks that it's in the range [0, upper). @@ -1005,7 +1019,7 @@ template ::value)> inline auto to_nonnegative_int(T value, Int upper) -> Int { if (!std::is_unsigned::value && (value < 0 || to_unsigned(value) > to_unsigned(upper))) { - FMT_THROW(fmt::format_error("chrono value is out of range")); + FMT_THROW(format_error("chrono value is out of range")); } return static_cast(value); } @@ -1090,7 +1104,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) { // Format subseconds which are given as a floating point type with an // appropriate number of digits. We cannot pass the Duration here, as we -// explicitly need to pass the Rep value in the chrono_formatter. +// explicitly need to pass the Rep value in the duration_formatter. template void write_floating_seconds(memory_buffer& buf, Duration duration, int num_fractional_digits = -1) { @@ -1124,7 +1138,7 @@ class tm_writer { static constexpr int days_per_week = 7; const std::locale& loc_; - const bool is_classic_; + bool is_classic_; OutputIt out_; const Duration* subsecs_; const std::tm& tm_; @@ -1160,8 +1174,8 @@ class tm_writer { } auto tm_hour12() const noexcept -> int { - const auto h = tm_hour(); - const auto z = h < 12 ? h : h - 12; + auto h = tm_hour(); + auto z = h < 12 ? h : h - 12; return z == 0 ? 12 : z; } @@ -1177,11 +1191,11 @@ class tm_writer { // Algorithm: https://en.wikipedia.org/wiki/ISO_week_date. auto iso_year_weeks(long long curr_year) const noexcept -> int { - const auto prev_year = curr_year - 1; - const auto curr_p = + auto prev_year = curr_year - 1; + auto curr_p = (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) % days_per_week; - const auto prev_p = + auto prev_p = (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) % days_per_week; return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0); @@ -1191,15 +1205,15 @@ class tm_writer { days_per_week; } auto tm_iso_week_year() const noexcept -> long long { - const auto year = tm_year(); - const auto w = iso_week_num(tm_yday(), tm_wday()); + auto year = tm_year(); + auto w = iso_week_num(tm_yday(), tm_wday()); if (w < 1) return year - 1; if (w > iso_year_weeks(year)) return year + 1; return year; } auto tm_iso_week_of_year() const noexcept -> int { - const auto year = tm_year(); - const auto w = iso_week_num(tm_yday(), tm_wday()); + auto year = tm_year(); + auto w = iso_week_num(tm_yday(), tm_wday()); if (w < 1) return iso_year_weeks(year - 1); if (w > iso_year_weeks(year)) return 1; return w; @@ -1236,9 +1250,8 @@ class tm_writer { uint32_or_64_or_128_t n = to_unsigned(year); const int num_digits = count_digits(n); if (negative && pad == pad_type::zero) *out_++ = '-'; - if (width > num_digits) { + if (width > num_digits) out_ = detail::write_padding(out_, pad, width - num_digits); - } if (negative && pad != pad_type::zero) *out_++ = '-'; out_ = format_decimal(out_, n, num_digits); } @@ -1259,45 +1272,22 @@ class tm_writer { write2(static_cast(offset % 60)); } - template ::value)> - void format_utc_offset_impl(const T& tm, numeric_system ns) { + template ::value)> + void format_utc_offset(const T& tm, numeric_system ns) { write_utc_offset(tm.tm_gmtoff, ns); } - template ::value)> - void format_utc_offset_impl(const T& tm, numeric_system ns) { -#if defined(_WIN32) && defined(_UCRT) - tzset_once(); - long offset = 0; - _get_timezone(&offset); - if (tm.tm_isdst) { - long dstbias = 0; - _get_dstbias(&dstbias); - offset += dstbias; - } - write_utc_offset(-offset, ns); -#else - if (ns == numeric_system::standard) return format_localized('z'); - - // Extract timezone offset from timezone conversion functions. - std::tm gtm = tm; - std::time_t gt = std::mktime(>m); - std::tm ltm = gmtime(gt); - std::time_t lt = std::mktime(<m); - long long offset = gt - lt; - write_utc_offset(offset, ns); -#endif + template ::value)> + void format_utc_offset(const T&, numeric_system ns) { + write_utc_offset(0, ns); } - template ::value)> - void format_tz_name_impl(const T& tm) { - if (is_classic_) - out_ = write_tm_str(out_, tm.tm_zone, loc_); - else - format_localized('Z'); + template ::value)> + void format_tz_name(const T& tm) { + out_ = write_tm_str(out_, tm.tm_zone, loc_); } - template ::value)> - void format_tz_name_impl(const T&) { - format_localized('Z'); + template ::value)> + void format_tz_name(const T&) { + out_ = std::copy_n(utc(), 3, out_); } void format_localized(char format, char modifier = 0) { @@ -1408,8 +1398,8 @@ class tm_writer { out_ = copy(std::begin(buf) + offset, std::end(buf), out_); } - void on_utc_offset(numeric_system ns) { format_utc_offset_impl(tm_, ns); } - void on_tz_name() { format_tz_name_impl(tm_); } + void on_utc_offset(numeric_system ns) { format_utc_offset(tm_, ns); } + void on_tz_name() { format_tz_name(tm_); } void on_year(numeric_system ns, pad_type pad) { if (is_classic_ || ns == numeric_system::standard) @@ -1483,11 +1473,10 @@ class tm_writer { void on_day_of_year(pad_type pad) { auto yday = tm_yday() + 1; auto digit1 = yday / 100; - if (digit1 != 0) { + if (digit1 != 0) write1(digit1); - } else { + else out_ = detail::write_padding(out_, pad); - } write2(yday % 100, pad); } @@ -1624,18 +1613,16 @@ template ::value)> inline auto get_milliseconds(std::chrono::duration d) -> std::chrono::duration { - // this may overflow and/or the result may not fit in the - // target type. + // This may overflow and/or the result may not fit in the target type. #if FMT_SAFE_DURATION_CAST - using CommonSecondsType = + using common_seconds_type = typename std::common_type::type; - const auto d_as_common = detail::duration_cast(d); - const auto d_as_whole_seconds = + auto d_as_common = detail::duration_cast(d); + auto d_as_whole_seconds = detail::duration_cast(d_as_common); - // this conversion should be nonproblematic - const auto diff = d_as_common - d_as_whole_seconds; - const auto ms = - detail::duration_cast>(diff); + // This conversion should be nonproblematic. + auto diff = d_as_common - d_as_whole_seconds; + auto ms = detail::duration_cast>(diff); return ms; #else auto s = detail::duration_cast(d); @@ -1707,32 +1694,28 @@ class get_locale { } }; -template -struct chrono_formatter { - FormatContext& context; - OutputIt out; - int precision; - bool localized = false; +template +struct duration_formatter { + using iterator = basic_appender; + iterator out; // rep is unsigned to avoid overflow. using rep = conditional_t::value && sizeof(Rep) < sizeof(int), unsigned, typename make_unsigned_or_unchanged::type>; rep val; + int precision; + locale_ref locale; + bool localized = false; using seconds = std::chrono::duration; seconds s; using milliseconds = std::chrono::duration; bool negative; - using char_type = typename FormatContext::char_type; - using tm_writer_type = tm_writer; + using tm_writer_type = tm_writer; - chrono_formatter(FormatContext& ctx, OutputIt o, - std::chrono::duration d) - : context(ctx), - out(o), - val(static_cast(d.count())), - negative(false) { + duration_formatter(iterator o, std::chrono::duration d, + locale_ref loc) + : out(o), val(static_cast(d.count())), locale(loc), negative(false) { if (d.count() < 0) { val = 0 - val; negative = true; @@ -1746,19 +1729,16 @@ struct chrono_formatter { // returns true if nan or inf, writes to out. auto handle_nan_inf() -> bool { - if (isfinite(val)) { - return false; - } + if (isfinite(val)) return false; if (isnan(val)) { write_nan(); return true; } // must be +-inf - if (val > 0) { - write_pinf(); - } else { - write_ninf(); - } + if (val > 0) + std::copy_n("inf", 3, out); + else + std::copy_n("-inf", 4, out); return true; } @@ -1786,10 +1766,9 @@ struct chrono_formatter { } void write_sign() { - if (negative) { - *out++ = '-'; - negative = false; - } + if (!negative) return; + *out++ = '-'; + negative = false; } void write(Rep value, int width, pad_type pad = pad_type::zero) { @@ -1801,24 +1780,22 @@ struct chrono_formatter { if (width > num_digits) { out = detail::write_padding(out, pad, width - num_digits); } - out = format_decimal(out, n, num_digits); + out = format_decimal(out, n, num_digits); } void write_nan() { std::copy_n("nan", 3, out); } - void write_pinf() { std::copy_n("inf", 3, out); } - void write_ninf() { std::copy_n("-inf", 4, out); } template void format_tm(const tm& time, Callback cb, Args... args) { if (isnan(val)) return write_nan(); - get_locale loc(localized, context.locale()); + get_locale loc(localized, locale); auto w = tm_writer_type(loc, out, time); (w.*cb)(args...); out = w.out(); } - void on_text(const char_type* begin, const char_type* end) { - copy(begin, end, out); + void on_text(const Char* begin, const Char* end) { + copy(begin, end, out); } // These are not implemented because durations don't have date information. @@ -1888,13 +1865,12 @@ struct chrono_formatter { write_floating_seconds(buf, std::chrono::duration(val), precision); if (negative) *out++ = '-'; - if (buf.size() < 2 || buf[1] == '.') { + if (buf.size() < 2 || buf[1] == '.') out = detail::write_padding(out, pad); - } - out = copy(buf.begin(), buf.end(), out); + out = copy(buf.begin(), buf.end(), out); } else { write(second(), 2, pad); - write_fractional_seconds( + write_fractional_seconds( out, std::chrono::duration(val), precision); } return; @@ -1936,12 +1912,10 @@ struct chrono_formatter { void on_duration_value() { if (handle_nan_inf()) return; write_sign(); - out = format_duration_value(out, val, precision); + out = format_duration_value(out, val, precision); } - void on_duration_unit() { - out = format_duration_unit(out); - } + void on_duration_unit() { out = format_duration_unit(out); } }; } // namespace detail @@ -2011,12 +1985,11 @@ class year_month_day { constexpr auto month() const noexcept -> fmt::month { return month_; } constexpr auto day() const noexcept -> fmt::day { return day_; } }; -#endif +#endif // __cpp_lib_chrono >= 201907 template struct formatter : private formatter { private: - bool localized_ = false; bool use_tm_formatter_ = false; public: @@ -2024,8 +1997,7 @@ struct formatter : private formatter { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == 'L') { ++it; - localized_ = true; - return it; + this->set_localized(); } use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; @@ -2036,7 +2008,7 @@ struct formatter : private formatter { auto time = std::tm(); time.tm_wday = static_cast(wd.c_encoding()); if (use_tm_formatter_) return formatter::format(time, ctx); - detail::get_locale loc(localized_, ctx.locale()); + detail::get_locale loc(this->localized(), ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_weekday(); return w.out(); @@ -2070,7 +2042,6 @@ struct formatter : private formatter { template struct formatter : private formatter { private: - bool localized_ = false; bool use_tm_formatter_ = false; public: @@ -2078,8 +2049,7 @@ struct formatter : private formatter { auto it = ctx.begin(), end = ctx.end(); if (it != end && *it == 'L') { ++it; - localized_ = true; - return it; + this->set_localized(); } use_tm_formatter_ = it != end && *it != '}'; return use_tm_formatter_ ? formatter::parse(ctx) : it; @@ -2090,7 +2060,7 @@ struct formatter : private formatter { auto time = std::tm(); time.tm_mon = static_cast(static_cast(m)) - 1; if (use_tm_formatter_) return formatter::format(time, ctx); - detail::get_locale loc(localized_, ctx.locale()); + detail::get_locale loc(this->localized(), ctx.locale()); auto w = detail::tm_writer(loc, ctx.out(), time); w.on_abbr_month(); return w.out(); @@ -2154,7 +2124,6 @@ struct formatter, Char> { format_specs specs_; detail::arg_ref width_ref_; detail::arg_ref precision_ref_; - bool localized_ = false; basic_string_view fmt_; public: @@ -2177,7 +2146,7 @@ struct formatter, Char> { it = detail::parse_precision(it, end, specs_, precision_ref_, ctx); } if (it != end && *it == 'L') { - localized_ = true; + specs_.set_localized(); ++it; } end = detail::parse_chrono_format(it, end, checker); @@ -2204,11 +2173,10 @@ struct formatter, Char> { out = detail::format_duration_value(out, d.count(), precision); detail::format_duration_unit(out); } else { - using chrono_formatter = - detail::chrono_formatter; - auto f = chrono_formatter(ctx, out, d); + auto f = + detail::duration_formatter(out, d, ctx.locale()); f.precision = precision; - f.localized = localized_; + f.localized = specs_.localized(); detail::parse_chrono_format(begin, end, f); } return detail::write( @@ -2220,30 +2188,15 @@ template struct formatter { private: format_specs specs_; detail::arg_ref width_ref_; + basic_string_view fmt_ = + detail::string_literal(); protected: - basic_string_view fmt_; + auto localized() const -> bool { return specs_.localized(); } + FMT_CONSTEXPR void set_localized() { specs_.set_localized(); } - template - auto do_format(const std::tm& tm, FormatContext& ctx, - const Duration* subsecs) const -> decltype(ctx.out()) { - auto specs = specs_; - auto buf = basic_memory_buffer(); - auto out = basic_appender(buf); - detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, - ctx); - - auto loc_ref = ctx.locale(); - detail::get_locale loc(static_cast(loc_ref), loc_ref); - auto w = - detail::tm_writer(loc, out, tm, subsecs); - detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); - return detail::write( - ctx.out(), basic_string_view(buf.data(), buf.size()), specs); - } - - public: - FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + FMT_CONSTEXPR auto do_parse(parse_context& ctx, bool has_timezone) + -> const Char* { auto it = ctx.begin(), end = ctx.end(); if (it == end || *it == '}') return it; @@ -2256,12 +2209,41 @@ template struct formatter { if (it == end) return it; } - end = detail::parse_chrono_format(it, end, detail::tm_format_checker()); + if (*it == 'L') { + specs_.set_localized(); + ++it; + } + + end = detail::parse_chrono_format(it, end, + detail::tm_format_checker(has_timezone)); // Replace the default format string only if the new spec is not empty. if (end != it) fmt_ = {it, detail::to_unsigned(end - it)}; return end; } + template + auto do_format(const std::tm& tm, FormatContext& ctx, + const Duration* subsecs) const -> decltype(ctx.out()) { + auto specs = specs_; + auto buf = basic_memory_buffer(); + auto out = basic_appender(buf); + detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, + ctx); + + auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref(); + detail::get_locale loc(static_cast(loc_ref), loc_ref); + auto w = detail::tm_writer, Char, Duration>( + loc, out, tm, subsecs); + detail::parse_chrono_format(fmt_.begin(), fmt_.end(), w); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); + } + + public: + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return do_parse(ctx, detail::has_tm_gmtoff::value); + } + template auto format(const std::tm& tm, FormatContext& ctx) const -> decltype(ctx.out()) { @@ -2269,10 +2251,11 @@ template struct formatter { } }; +// DEPRECATED! Reversed order of template parameters. template -struct formatter, Char> : formatter { - FMT_CONSTEXPR formatter() { - this->fmt_ = detail::string_literal(); +struct formatter, Char> : private formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return this->do_parse(ctx, true); } template @@ -2283,6 +2266,7 @@ struct formatter, Char> : formatter { if (detail::const_check( period::num == 1 && period::den == 1 && !std::is_floating_point::value)) { + detail::set_tm_zone(tm, detail::utc()); return formatter::format(tm, ctx); } Duration epoch = val.time_since_epoch(); @@ -2290,11 +2274,13 @@ struct formatter, Char> : formatter { epoch - detail::duration_cast(epoch)); if (subsecs.count() < 0) { auto second = detail::duration_cast(std::chrono::seconds(1)); - if (tm.tm_sec != 0) + if (tm.tm_sec != 0) { --tm.tm_sec; - else + } else { tm = gmtime(val - second); - subsecs += detail::duration_cast(std::chrono::seconds(1)); + detail::set_tm_zone(tm, detail::utc()); + } + subsecs += second; } return formatter::do_format(tm, ctx, &subsecs); } @@ -2312,23 +2298,29 @@ struct formatter, Char> }; template -struct formatter, Char> : formatter { - FMT_CONSTEXPR formatter() { - this->fmt_ = detail::string_literal(); +struct formatter, Char> + : private formatter { + FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { + return this->do_parse(ctx, false); } template auto format(local_time val, FormatContext& ctx) const -> decltype(ctx.out()) { + auto time_since_epoch = val.time_since_epoch(); + auto seconds_since_epoch = + detail::duration_cast(time_since_epoch); + // Use gmtime to prevent time zone conversion since local_time has an + // unspecified time zone. + std::tm t = gmtime(seconds_since_epoch.count()); using period = typename Duration::period; if (period::num == 1 && period::den == 1 && !std::is_floating_point::value) { - return formatter::format(localtime(val), ctx); + return formatter::format(t, ctx); } - auto epoch = val.time_since_epoch(); - auto subsecs = detail::duration_cast( - epoch - detail::duration_cast(epoch)); - return formatter::do_format(localtime(val), ctx, &subsecs); + auto subsecs = + detail::duration_cast(time_since_epoch - seconds_since_epoch); + return formatter::do_format(t, ctx, &subsecs); } }; diff --git a/include/fmt/color.h b/include/fmt/color.h index 2faaf3a0..638f15b4 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -190,11 +190,11 @@ enum class emphasis : uint8_t { // rgb is a struct for red, green and blue colors. // Using the name "rgb" makes some editors show the color in a tooltip. struct rgb { - FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {} - FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} - FMT_CONSTEXPR rgb(uint32_t hex) + constexpr rgb() : r(0), g(0), b(0) {} + constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {} + constexpr rgb(uint32_t hex) : r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {} - FMT_CONSTEXPR rgb(color hex) + constexpr rgb(color hex) : r((uint32_t(hex) >> 16) & 0xFF), g((uint32_t(hex) >> 8) & 0xFF), b(uint32_t(hex) & 0xFF) {} @@ -205,97 +205,135 @@ struct rgb { namespace detail { -// color is a struct of either a rgb color or a terminal color. +// A bit-packed variant of an RGB color, a terminal color, or unset color. +// see text_style for the bit-packing scheme. struct color_type { - FMT_CONSTEXPR color_type() noexcept : is_rgb(), value{} {} - FMT_CONSTEXPR color_type(color rgb_color) noexcept : is_rgb(true), value{} { - value.rgb_color = static_cast(rgb_color); + constexpr color_type() noexcept = default; + constexpr color_type(color rgb_color) noexcept + : value_(static_cast(rgb_color) | (1 << 24)) {} + constexpr color_type(rgb rgb_color) noexcept + : color_type(static_cast( + (static_cast(rgb_color.r) << 16) | + (static_cast(rgb_color.g) << 8) | rgb_color.b)) {} + constexpr color_type(terminal_color term_color) noexcept + : value_(static_cast(term_color) | (3 << 24)) {} + + constexpr auto is_terminal_color() const noexcept -> bool { + return (value_ & (1 << 25)) != 0; } - FMT_CONSTEXPR color_type(rgb rgb_color) noexcept : is_rgb(true), value{} { - value.rgb_color = (static_cast(rgb_color.r) << 16) | - (static_cast(rgb_color.g) << 8) | rgb_color.b; + + constexpr auto value() const noexcept -> uint32_t { + return value_ & 0xFFFFFF; } - FMT_CONSTEXPR color_type(terminal_color term_color) noexcept - : is_rgb(), value{} { - value.term_color = static_cast(term_color); - } - bool is_rgb; - union color_union { - uint8_t term_color; - uint32_t rgb_color; - } value; + + constexpr color_type(uint32_t value) noexcept : value_(value) {} + + uint32_t value_ = 0; }; } // namespace detail /// A text style consisting of foreground and background colors and emphasis. class text_style { + // The information is packed as follows: + // ┌──┐ + // │ 0│─┐ + // │..│ ├── foreground color value + // │23│─┘ + // ├──┤ + // │24│─┬── discriminator for the above value. 00 if unset, 01 if it's + // │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused) + // ├──┤ + // │26│──── overflow bit, always zero (see below) + // ├──┤ + // │27│─┐ + // │..│ │ + // │50│ │ + // ├──┤ │ + // │51│ ├── background color (same format as the foreground color) + // │52│ │ + // ├──┤ │ + // │53│─┘ + // ├──┤ + // │54│─┐ + // │..│ ├── emphases + // │61│─┘ + // ├──┤ + // │62│─┬── unused + // │63│─┘ + // └──┘ + // The overflow bits are there to make operator|= efficient. + // When ORing, we must throw if, for either the foreground or background, + // one style specifies a terminal color and the other specifies any color + // (terminal or RGB); in other words, if one discriminator is 11 and the + // other is 11 or 01. + // + // We do that check by adding the styles. Consider what adding does to each + // possible pair of discriminators: + // 00 + 00 = 000 + // 01 + 00 = 001 + // 11 + 00 = 011 + // 01 + 01 = 010 + // 11 + 01 = 100 (!!) + // 11 + 11 = 110 (!!) + // In the last two cases, the ones we want to catch, the third bit——the + // overflow bit——is set. Bingo. + // + // We must take into account the possible carry bit from the bits + // before the discriminator. The only potentially problematic case is + // 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry + // bit is impossible in that case, because 00 (unset color) means the + // 24 bits that precede the discriminator are all zero. + // + // This test can be applied to both colors simultaneously. + public: FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept - : set_foreground_color(), set_background_color(), ems(em) {} + : style_(static_cast(em) << 54) {} - FMT_CONSTEXPR auto operator|=(const text_style& rhs) -> text_style& { - if (!set_foreground_color) { - set_foreground_color = rhs.set_foreground_color; - foreground_color = rhs.foreground_color; - } else if (rhs.set_foreground_color) { - if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb) - report_error("can't OR a terminal color"); - foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color; - } - - if (!set_background_color) { - set_background_color = rhs.set_background_color; - background_color = rhs.background_color; - } else if (rhs.set_background_color) { - if (!background_color.is_rgb || !rhs.background_color.is_rgb) - report_error("can't OR a terminal color"); - background_color.value.rgb_color |= rhs.background_color.value.rgb_color; - } - - ems = static_cast(static_cast(ems) | - static_cast(rhs.ems)); + FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& { + if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0) + report_error("can't OR a terminal color"); + style_ |= rhs.style_; return *this; } - friend FMT_CONSTEXPR auto operator|(text_style lhs, const text_style& rhs) + friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs) -> text_style { return lhs |= rhs; } + FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool { + return style_ == rhs.style_; + } + + FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool { + return !(*this == rhs); + } + FMT_CONSTEXPR auto has_foreground() const noexcept -> bool { - return set_foreground_color; + return (style_ & (1 << 24)) != 0; } FMT_CONSTEXPR auto has_background() const noexcept -> bool { - return set_background_color; + return (style_ & (1ULL << 51)) != 0; } FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool { - return static_cast(ems) != 0; + return (style_ >> 54) != 0; } FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); - return foreground_color; + return style_ & 0x3FFFFFF; } FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type { FMT_ASSERT(has_background(), "no background specified for this style"); - return background_color; + return (style_ >> 27) & 0x3FFFFFF; } FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis { FMT_ASSERT(has_emphasis(), "no emphasis specified for this style"); - return ems; + return static_cast(style_ >> 54); } private: - FMT_CONSTEXPR text_style(bool is_foreground, - detail::color_type text_color) noexcept - : set_foreground_color(), set_background_color(), ems() { - if (is_foreground) { - foreground_color = text_color; - set_foreground_color = true; - } else { - background_color = text_color; - set_background_color = true; - } - } + FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {} friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept -> text_style; @@ -303,23 +341,19 @@ class text_style { friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept -> text_style; - detail::color_type foreground_color; - detail::color_type background_color; - bool set_foreground_color; - bool set_background_color; - emphasis ems; + uint64_t style_ = 0; }; /// Creates a text style from the foreground (text) color. FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept -> text_style { - return text_style(true, foreground); + return foreground.value_; } /// Creates a text style from the background color. FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept -> text_style { - return text_style(false, background); + return static_cast(background.value_) << 27; } FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept @@ -334,9 +368,9 @@ template struct ansi_color_escape { const char* esc) noexcept { // If we have a terminal color, we need to output another escape code // sequence. - if (!text_color.is_rgb) { + if (text_color.is_terminal_color()) { bool is_background = esc == string_view("\x1b[48;2;"); - uint32_t value = text_color.value.term_color; + uint32_t value = text_color.value(); // Background ASCII codes are the same as the foreground ones but with // 10 more. if (is_background) value += 10u; @@ -360,7 +394,7 @@ template struct ansi_color_escape { for (int i = 0; i < 7; i++) { buffer[i] = static_cast(esc[i]); } - rgb color(text_color.value.rgb_color); + rgb color(text_color.value()); to_esc(color.r, buffer + 7, ';'); to_esc(color.g, buffer + 11, ';'); to_esc(color.b, buffer + 15, 'm'); @@ -441,32 +475,26 @@ template struct styled_arg : view { }; template -void vformat_to(buffer& buf, const text_style& ts, - basic_string_view fmt, +void vformat_to(buffer& buf, text_style ts, basic_string_view fmt, basic_format_args> args) { - bool has_style = false; if (ts.has_emphasis()) { - has_style = true; auto emphasis = make_emphasis(ts.get_emphasis()); buf.append(emphasis.begin(), emphasis.end()); } if (ts.has_foreground()) { - has_style = true; auto foreground = make_foreground_color(ts.get_foreground()); buf.append(foreground.begin(), foreground.end()); } if (ts.has_background()) { - has_style = true; auto background = make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } vformat_to(buf, fmt, args); - if (has_style) reset_color(buf); + if (ts != text_style()) reset_color(buf); } } // namespace detail -inline void vprint(FILE* f, const text_style& ts, string_view fmt, - format_args args) { +inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) { auto buf = memory_buffer(); detail::vformat_to(buf, ts, fmt, args); print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size())); @@ -482,8 +510,7 @@ inline void vprint(FILE* f, const text_style& ts, string_view fmt, * "Elapsed time: {0:.2f} seconds", 1.23); */ template -void print(FILE* f, const text_style& ts, format_string fmt, - T&&... args) { +void print(FILE* f, text_style ts, format_string fmt, T&&... args) { vprint(f, ts, fmt.str, vargs{{args...}}); } @@ -497,11 +524,11 @@ void print(FILE* f, const text_style& ts, format_string fmt, * "Elapsed time: {0:.2f} seconds", 1.23); */ template -void print(const text_style& ts, format_string fmt, T&&... args) { +void print(text_style ts, format_string fmt, T&&... args) { return print(stdout, ts, fmt, std::forward(args)...); } -inline auto vformat(const text_style& ts, string_view fmt, format_args args) +inline auto vformat(text_style ts, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); detail::vformat_to(buf, ts, fmt, args); @@ -521,7 +548,7 @@ inline auto vformat(const text_style& ts, string_view fmt, format_args args) * ``` */ template -inline auto format(const text_style& ts, format_string fmt, T&&... args) +inline auto format(text_style ts, format_string fmt, T&&... args) -> std::string { return fmt::vformat(ts, fmt.str, vargs{{args...}}); } @@ -529,8 +556,8 @@ inline auto format(const text_style& ts, format_string fmt, T&&... args) /// Formats a string with the given text_style and writes the output to `out`. template ::value)> -auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, - format_args args) -> OutputIt { +auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args) + -> OutputIt { auto&& buf = detail::get_buffer(out); detail::vformat_to(buf, ts, fmt, args); return detail::get_iterator(buf, out); @@ -548,8 +575,8 @@ auto vformat_to(OutputIt out, const text_style& ts, string_view fmt, */ template ::value)> -inline auto format_to(OutputIt out, const text_style& ts, - format_string fmt, T&&... args) -> OutputIt { +inline auto format_to(OutputIt out, text_style ts, format_string fmt, + T&&... args) -> OutputIt { return vformat_to(out, ts, fmt.str, vargs{{args...}}); } diff --git a/include/fmt/compile.h b/include/fmt/compile.h index 68b451c7..af787718 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -19,11 +19,11 @@ FMT_BEGIN_NAMESPACE // A compile-time string which is compiled into fast formatting code. FMT_EXPORT class compiled_string {}; -namespace detail { - template struct is_compiled_string : std::is_base_of {}; +namespace detail { + /** * Converts a string literal `s` into a format string that will be parsed at * compile time and converted into efficient formatting code. Requires C++17 @@ -41,18 +41,8 @@ struct is_compiled_string : std::is_base_of {}; # define FMT_COMPILE(s) FMT_STRING(s) #endif -#if FMT_USE_NONTYPE_TEMPLATE_ARGS -template Str> -struct udl_compiled_string : compiled_string { - using char_type = Char; - constexpr explicit operator basic_string_view() const { - return {Str.data, N - 1}; - } -}; -#endif - template -auto first(const T& value, const Tail&...) -> const T& { +constexpr auto first(const T& value, const Tail&...) -> const T& { return value; } @@ -425,7 +415,7 @@ constexpr auto compile_format_string(S fmt) { } template ::value)> + FMT_ENABLE_IF(is_compiled_string::value)> constexpr auto compile(S fmt) { constexpr auto str = basic_string_view(fmt); if constexpr (str.size() == 0) { @@ -446,8 +436,8 @@ FMT_BEGIN_EXPORT template ::value)> -FMT_INLINE std::basic_string format(const CompiledFormat& cf, - const Args&... args) { +FMT_INLINE FMT_CONSTEXPR_STRING std::basic_string format( + const CompiledFormat& cf, const Args&... args) { auto s = std::basic_string(); cf.format(std::back_inserter(s), args...); return s; @@ -461,9 +451,9 @@ constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf, } template ::value)> -FMT_INLINE std::basic_string format(const S&, - Args&&... args) { + FMT_ENABLE_IF(is_compiled_string::value)> +FMT_INLINE FMT_CONSTEXPR_STRING std::basic_string format( + const S&, Args&&... args) { if constexpr (std::is_same::value) { constexpr auto str = basic_string_view(S()); if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') { @@ -488,7 +478,7 @@ FMT_INLINE std::basic_string format(const S&, } template ::value)> + FMT_ENABLE_IF(is_compiled_string::value)> FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { constexpr auto compiled = detail::compile(S()); if constexpr (std::is_same, @@ -503,7 +493,7 @@ FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) { #endif template ::value)> + FMT_ENABLE_IF(is_compiled_string::value)> auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) -> format_to_n_result { using traits = detail::fixed_buffer_traits; @@ -513,7 +503,7 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, Args&&... args) } template ::value)> + FMT_ENABLE_IF(is_compiled_string::value)> FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) -> size_t { auto buf = detail::counting_buffer<>(); @@ -522,7 +512,7 @@ FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args) } template ::value)> + FMT_ENABLE_IF(is_compiled_string::value)> void print(std::FILE* f, const S& fmt, const Args&... args) { auto buf = memory_buffer(); fmt::format_to(appender(buf), fmt, args...); @@ -530,7 +520,7 @@ void print(std::FILE* f, const S& fmt, const Args&... args) { } template ::value)> + FMT_ENABLE_IF(is_compiled_string::value)> void print(const S& fmt, const Args&... args) { print(stdout, fmt, args...); } @@ -538,9 +528,7 @@ void print(const S& fmt, const Args&... args) { #if FMT_USE_NONTYPE_TEMPLATE_ARGS inline namespace literals { template constexpr auto operator""_cf() { - using char_t = remove_cvref_t; - return detail::udl_compiled_string(); + return FMT_COMPILE(Str.data); } } // namespace literals #endif diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 14c65a09..f0b68846 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -22,7 +22,7 @@ #include "format.h" -#if FMT_USE_LOCALE +#if FMT_USE_LOCALE && !defined(FMT_MODULE) # include #endif @@ -84,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, ""); } @@ -212,7 +212,7 @@ inline auto floor_log10_pow2_minus_log10_4_over_3(int e) noexcept -> int { return (e * 631305 - 261663) >> 21; } -FMT_INLINE_VARIABLE constexpr struct { +FMT_INLINE_VARIABLE constexpr struct div_small_pow10_infos_struct { uint32_t divisor; int shift_amount; } div_small_pow10_infos[] = {{10, 16}, {100, 16}}; @@ -275,7 +275,7 @@ template <> struct cache_accessor { static auto get_cached_power(int k) noexcept -> uint64_t { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); - static constexpr const uint64_t pow10_significands[] = { + static constexpr uint64_t pow10_significands[] = { 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb, 0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28, @@ -370,7 +370,7 @@ template <> struct cache_accessor { FMT_ASSERT(k >= float_info::min_k && k <= float_info::max_k, "k is out of range"); - static constexpr const uint128_fallback pow10_significands[] = { + static constexpr uint128_fallback pow10_significands[] = { #if FMT_USE_FULL_CACHE_DRAGONBOX {0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b}, {0x9faacf3df73609b1, 0x77b191618c54e9ad}, @@ -1037,7 +1037,7 @@ template <> struct cache_accessor { #if FMT_USE_FULL_CACHE_DRAGONBOX return pow10_significands[k - float_info::min_k]; #else - static constexpr const uint64_t powers_of_5_64[] = { + static constexpr uint64_t powers_of_5_64[] = { 0x0000000000000001, 0x0000000000000005, 0x0000000000000019, 0x000000000000007d, 0x0000000000000271, 0x0000000000000c35, 0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1, @@ -1097,7 +1097,7 @@ template <> struct cache_accessor { return {r.high(), r.low() == 0}; } - static auto compute_delta(cache_entry_type const& cache, int beta) noexcept + static auto compute_delta(const cache_entry_type& cache, int beta) noexcept -> uint32_t { return static_cast(cache.high() >> (64 - 1 - beta)); } @@ -1526,9 +1526,8 @@ template class glibc_file : public file_base { } void init_buffer() { - if (this->file_->_IO_write_ptr) return; + if (this->file_->_IO_write_ptr < this->file_->_IO_write_end) return; // Force buffer initialization by placing and removing a char in a buffer. - assume(this->file_->_IO_write_ptr >= this->file_->_IO_write_end); putc_unlocked(0, this->file_); --this->file_->_IO_write_ptr; } diff --git a/include/fmt/format.h b/include/fmt/format.h index c9a6054d..1bf44f99 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -44,6 +44,7 @@ # include // std::signbit # include // std::byte # include // uint32_t +# include // std::malloc, std::free # include // std::memcpy # include // std::numeric_limits # include // std::bad_alloc @@ -117,6 +118,34 @@ # define FMT_NOINLINE #endif +// Detect constexpr std::string. +#if !FMT_USE_CONSTEVAL +# define FMT_USE_CONSTEXPR_STRING 0 +#elif defined(__cpp_lib_constexpr_string) && \ + __cpp_lib_constexpr_string >= 201907L +# if FMT_CLANG_VERSION && FMT_GLIBCXX_RELEASE +// clang + libstdc++ are able to work only starting with gcc13.3 +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113294 +# if FMT_GLIBCXX_RELEASE < 13 +# define FMT_USE_CONSTEXPR_STRING 0 +# elif FMT_GLIBCXX_RELEASE == 13 && __GLIBCXX__ < 20240521 +# define FMT_USE_CONSTEXPR_STRING 0 +# else +# define FMT_USE_CONSTEXPR_STRING 1 +# endif +# else +# define FMT_USE_CONSTEXPR_STRING 1 +# endif +#else +# define FMT_USE_CONSTEXPR_STRING 0 +#endif +#if FMT_USE_CONSTEXPR_STRING +# define FMT_CONSTEXPR_STRING constexpr +#else +# define FMT_CONSTEXPR_STRING +#endif + +// GCC 4.9 doesn't support qualified names in specializations. namespace std { template struct iterator_traits> { using iterator_category = output_iterator_tag; @@ -227,7 +256,9 @@ FMT_CONSTEXPR inline void abort_fuzzing_if(bool condition) { #if defined(FMT_USE_STRING_VIEW) template using std_string_view = std::basic_string_view; #else -template struct std_string_view {}; +template struct std_string_view { + operator basic_string_view() const; +}; #endif template struct string_literal { @@ -523,6 +554,8 @@ FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value) template FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* { if (is_constant_evaluated()) return fill_n(out, count, value); + static_assert(sizeof(T) == 1, + "sizeof(T) must be 1 to use char for initialization"); std::memset(out, value, to_unsigned(count)); return out + count; } @@ -552,10 +585,10 @@ FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end, */ FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e) -> const char* { - constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; - constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; - constexpr const int shiftc[] = {0, 18, 12, 6, 0}; - constexpr const int shifte[] = {0, 6, 4, 2, 0}; + constexpr int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07}; + constexpr uint32_t mins[] = {4194304, 0, 128, 2048, 65536}; + constexpr int shiftc[] = {0, 18, 12, 6, 0}; + constexpr int shifte[] = {0, 6, 4, 2, 0}; int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4" [static_cast(*s) >> 3]; @@ -666,26 +699,6 @@ FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t { return num_code_points; } -template -inline auto code_point_index(basic_string_view s, size_t n) -> size_t { - return min_of(n, s.size()); -} - -// Calculates the index of the nth code point in a UTF-8 string. -inline auto code_point_index(string_view s, size_t n) -> size_t { - size_t result = s.size(); - const char* begin = s.begin(); - for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) { - if (n != 0) { - --n; - return true; - } - result = to_unsigned(sv.begin() - begin); - return false; - }); - return result; -} - template struct is_integral : std::is_integral {}; template <> struct is_integral : std::true_type {}; template <> struct is_integral : std::true_type {}; @@ -703,7 +716,7 @@ using is_integer = #if defined(FMT_USE_FLOAT128) // Use the provided definition. -#elif FMT_CLANG_VERSION && FMT_HAS_INCLUDE() +#elif FMT_CLANG_VERSION >= 309 && FMT_HAS_INCLUDE() # define FMT_USE_FLOAT128 1 #elif FMT_GCC_VERSION && defined(_GLIBCXX_USE_FLOAT128) && \ !defined(__STRICT_ANSI__) @@ -719,15 +732,17 @@ struct float128 {}; template using is_float128 = std::is_same; -template -using is_floating_point = - bool_constant::value || is_float128::value>; +template struct is_floating_point : std::is_floating_point {}; +template <> struct is_floating_point : std::true_type {}; -template ::value> +template ::value> struct is_fast_float : bool_constant::is_iec559 && sizeof(T) <= sizeof(double)> {}; template struct is_fast_float : std::false_type {}; +template +using fast_float_t = conditional_t; + template using is_double_double = bool_constant::digits == 106>; @@ -736,18 +751,19 @@ using is_double_double = bool_constant::digits == 106>; #endif // An allocator that uses malloc/free to allow removing dependency on the C++ -// standard libary runtime. -template struct allocator { +// standard libary runtime. std::decay is used for back_inserter to be found by +// ADL when applied to memory_buffer. +template struct allocator : private std::decay { using value_type = T; T* allocate(size_t n) { FMT_ASSERT(n <= max_value() / sizeof(T), ""); - T* p = static_cast(malloc(n * sizeof(T))); + T* p = static_cast(std::malloc(n * sizeof(T))); if (!p) FMT_THROW(std::bad_alloc()); return p; } - void deallocate(T* p, size_t) { free(p); } + void deallocate(T* p, size_t) { std::free(p); } }; } // namespace detail @@ -1042,7 +1058,7 @@ inline auto do_count_digits(uint64_t n) -> int { 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15, 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20}; auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63]; - static constexpr const uint64_t zero_or_powers_of_10[] = { + static constexpr uint64_t zero_or_powers_of_10[] = { 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL), 10000000000000000000ULL}; return t - (n < zero_or_powers_of_10[t]); @@ -1203,7 +1219,7 @@ FMT_CONSTEXPR FMT_INLINE auto format_decimal(Char* out, UInt value, } template ::value)> + FMT_ENABLE_IF(!std::is_pointer>::value)> FMT_CONSTEXPR auto format_decimal(OutputIt out, UInt value, int num_digits) -> OutputIt { if (auto ptr = to_pointer(out, to_unsigned(num_digits))) { @@ -1223,7 +1239,7 @@ FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value, out += size; do { const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; - unsigned digit = static_cast(value & ((1 << base_bits) - 1)); + unsigned digit = static_cast(value & ((1u << base_bits) - 1)); *--out = static_cast(base_bits < 4 ? static_cast('0' + digit) : digits[digit]); } while ((value >>= base_bits) != 0); @@ -1485,6 +1501,13 @@ template constexpr auto exponent_bias() -> int { : std::numeric_limits::max_exponent - 1; } +FMT_CONSTEXPR inline auto compute_exp_size(int exp) -> int { + auto prefix_size = 2; // sign + 'e' + auto abs_exp = exp >= 0 ? exp : -exp; + if (exp < 100) return prefix_size + 2; + return prefix_size + (abs_exp >= 1000 ? 4 : 3); +} + // Writes the exponent exp in the form "[+-]d{2,3}" to buffer. template FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt { @@ -1517,7 +1540,7 @@ template struct basic_fp { F f; int e; - static constexpr const int num_significand_bits = + static constexpr int num_significand_bits = static_cast(sizeof(F) * num_bits()); constexpr basic_fp() : f(0), e(0) {} @@ -1611,7 +1634,7 @@ constexpr auto convert_float(T value) -> convert_float_result { } template -FMT_NOINLINE FMT_CONSTEXPR auto fill(OutputIt it, size_t n, +FMT_CONSTEXPR FMT_NOINLINE auto fill(OutputIt it, size_t n, const basic_specs& specs) -> OutputIt { auto fill_size = specs.fill_size(); if (fill_size == 1) return detail::fill_n(it, n, specs.fill_unit()); @@ -1839,7 +1862,9 @@ template class digit_grouping { } public: - explicit digit_grouping(locale_ref loc, bool localized = true) { + template ::value)> + explicit digit_grouping(Locale loc, bool localized = true) { if (!localized) return; auto sep = thousands_sep(loc); grouping_ = sep.grouping; @@ -1960,8 +1985,7 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign s) prefix = 0x01000000 | '-'; abs_value = 0 - abs_value; } else { - constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+', - 0x1000000u | ' '}; + constexpr unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '}; prefix = prefixes[static_cast(s)]; } return {abs_value, prefix}; @@ -2014,7 +2038,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg arg, const format_specs& specs) -> OutputIt { static_assert(std::is_same>::value, ""); - constexpr int buffer_size = num_bits(); + constexpr size_t buffer_size = num_bits(); char buffer[buffer_size]; if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0'); const char* begin = nullptr; @@ -2106,13 +2130,35 @@ FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value, return write_int(out, make_write_int_arg(value, specs.sign()), specs); } +inline auto convert_precision_to_size(string_view s, size_t precision) + -> size_t { + size_t display_width = 0; + size_t result = s.size(); + for_each_codepoint(s, [&](uint32_t, string_view sv) { + display_width += compute_width(sv); + // Stop when display width exceeds precision. + if (display_width > precision) { + result = to_unsigned(sv.begin() - s.begin()); + return false; + } + return true; + }); + return result; +} + +template ::value)> +auto convert_precision_to_size(basic_string_view, size_t precision) + -> size_t { + return precision; +} + template FMT_CONSTEXPR auto write(OutputIt out, basic_string_view s, const format_specs& specs) -> OutputIt { auto data = s.data(); auto size = s.size(); if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); + size = convert_precision_to_size(s, to_unsigned(specs.precision)); bool is_debug = specs.type() == presentation_type::debug; if (is_debug) { @@ -2270,7 +2316,7 @@ inline auto write_significand(Char* out, UInt significand, int significand_size, int floating_size = significand_size - integral_size; for (int i = floating_size / 2; i > 0; --i) { out -= 2; - write2digits(out, static_cast(significand % 100)); + write2digits(out, static_cast(significand % 100)); significand /= 100; } if (floating_size % 2 != 0) { @@ -2324,110 +2370,18 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand, buffer.end(), out); } -template > -FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, - const format_specs& specs, sign s, - locale_ref loc) -> OutputIt { - auto significand = f.significand; - int significand_size = get_significand_size(f); - const Char zero = static_cast('0'); - size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0); - using iterator = reserve_iterator; +// Numbers with exponents greater or equal to the returned value will use +// the exponential notation. +template FMT_CONSTEVAL auto exp_upper() -> int { + return std::numeric_limits::digits10 != 0 + ? min_of(16, std::numeric_limits::digits10 + 1) + : 16; +} - Char decimal_point = specs.localized() ? detail::decimal_point(loc) - : static_cast('.'); - - int output_exp = f.exponent + significand_size - 1; - auto use_exp_format = [=]() { - if (specs.type() == presentation_type::exp) return true; - if (specs.type() == presentation_type::fixed) return false; - // Use the fixed notation if the exponent is in [exp_lower, exp_upper), - // e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. - const int exp_lower = -4, exp_upper = 16; - return output_exp < exp_lower || - output_exp >= (specs.precision > 0 ? specs.precision : exp_upper); - }; - if (use_exp_format()) { - int num_zeros = 0; - if (specs.alt()) { - num_zeros = specs.precision - significand_size; - if (num_zeros < 0) num_zeros = 0; - size += to_unsigned(num_zeros); - } else if (significand_size == 1) { - decimal_point = Char(); - } - auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp; - int exp_digits = 2; - if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3; - - size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits); - char exp_char = specs.upper() ? 'E' : 'e'; - auto write = [=](iterator it) { - if (s != sign::none) *it++ = detail::getsign(s); - // Insert a decimal point after the first digit and add an exponent. - it = write_significand(it, significand, significand_size, 1, - decimal_point); - if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero); - *it++ = static_cast(exp_char); - return write_exponent(output_exp, it); - }; - return specs.width > 0 - ? write_padded(out, specs, size, write) - : base_iterator(out, write(reserve(out, size))); - } - - int exp = f.exponent + significand_size; - if (f.exponent >= 0) { - // 1234e5 -> 123400000[.0+] - size += to_unsigned(f.exponent); - int num_zeros = specs.precision - exp; - abort_fuzzing_if(num_zeros > 5000); - if (specs.alt()) { - ++size; - if (num_zeros <= 0 && specs.type() != presentation_type::fixed) - num_zeros = 0; - if (num_zeros > 0) size += to_unsigned(num_zeros); - } - auto grouping = Grouping(loc, specs.localized()); - size += to_unsigned(grouping.count_separators(exp)); - return write_padded(out, specs, size, [&](iterator it) { - if (s != sign::none) *it++ = detail::getsign(s); - it = write_significand(it, significand, significand_size, - f.exponent, grouping); - if (!specs.alt()) return it; - *it++ = decimal_point; - return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; - }); - } else if (exp > 0) { - // 1234e-2 -> 12.34[0+] - int num_zeros = specs.alt() ? specs.precision - significand_size : 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) { - if (s != sign::none) *it++ = detail::getsign(s); - it = write_significand(it, significand, significand_size, exp, - decimal_point, grouping); - return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it; - }); - } - // 1234e-6 -> 0.001234 - int num_zeros = -exp; - if (significand_size == 0 && specs.precision >= 0 && - specs.precision < num_zeros) { - num_zeros = specs.precision; - } - bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); - size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros); - return write_padded(out, specs, size, [&](iterator it) { - if (s != sign::none) *it++ = detail::getsign(s); - *it++ = zero; - if (!pointy) return it; - *it++ = decimal_point; - it = detail::fill_n(it, num_zeros, zero); - return write_significand(it, significand, significand_size); - }); +// Use the fixed notation if the exponent is in [-4, exp_upper), +// e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation. +constexpr auto use_fixed(int exp, int exp_upper) -> bool { + return exp >= -4 && exp < exp_upper; } template class fallback_digit_grouping { @@ -2444,15 +2398,122 @@ template class fallback_digit_grouping { } }; +template +FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f, + int significand_size, Char decimal_point, + const format_specs& specs, sign s, + locale_ref loc = {}) -> OutputIt { + using iterator = reserve_iterator; + + int exp = f.exponent + significand_size; + long long size = significand_size + (s != sign::none ? 1 : 0); + if (f.exponent >= 0) { + // 1234e5 -> 123400000[.0+] + size += f.exponent; + int num_zeros = specs.precision - exp; + abort_fuzzing_if(num_zeros > 5000); + if (specs.alt()) { + ++size; + if (num_zeros <= 0 && specs.type() != presentation_type::fixed) + num_zeros = 0; + if (num_zeros > 0) size += num_zeros; + } + auto grouping = Grouping(loc, specs.localized()); + size += grouping.count_separators(exp); + return write_padded( + out, specs, to_unsigned(size), [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, f.significand, significand_size, + f.exponent, grouping); + if (!specs.alt()) return it; + *it++ = decimal_point; + return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it; + }); + } + if (exp > 0) { + // 1234e-2 -> 12.34[0+] + int num_zeros = specs.alt() ? specs.precision - significand_size : 0; + size += 1 + max_of(num_zeros, 0); + auto grouping = Grouping(loc, specs.localized()); + size += grouping.count_separators(exp); + return write_padded( + out, specs, to_unsigned(size), [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + it = write_significand(it, f.significand, significand_size, exp, + decimal_point, grouping); + return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it; + }); + } + // 1234e-6 -> 0.001234 + int num_zeros = -exp; + if (significand_size == 0 && specs.precision >= 0 && + specs.precision < num_zeros) { + num_zeros = specs.precision; + } + bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); + size += 1 + (pointy ? 1 : 0) + num_zeros; + return write_padded( + out, specs, to_unsigned(size), [&](iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + *it++ = Char('0'); + if (!pointy) return it; + *it++ = decimal_point; + it = detail::fill_n(it, num_zeros, Char('0')); + return write_significand(it, f.significand, significand_size); + }); +} + +template +FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f, + const format_specs& specs, sign s, + int exp_upper, locale_ref loc) -> OutputIt { + Char point = specs.localized() ? detail::decimal_point(loc) : Char('.'); + int significand_size = get_significand_size(f); + int exp = f.exponent + significand_size - 1; + if (specs.type() == presentation_type::fixed || + (specs.type() != presentation_type::exp && + use_fixed(exp, specs.precision > 0 ? specs.precision : exp_upper))) { + return write_fixed(out, f, significand_size, point, specs, + s, loc); + } + + // Write value in the exponential format. + int num_zeros = 0; + long long size = significand_size + (s != sign::none ? 1 : 0); + if (specs.alt()) { + num_zeros = max_of(specs.precision - significand_size, 0); + size += num_zeros; + } else if (significand_size == 1) { + point = Char(); + } + size += (point ? 1 : 0) + compute_exp_size(exp); + char exp_char = specs.upper() ? 'E' : 'e'; + auto write = [=](reserve_iterator it) { + if (s != sign::none) *it++ = detail::getsign(s); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, f.significand, significand_size, 1, point); + if (num_zeros > 0) it = detail::fill_n(it, num_zeros, Char('0')); + *it++ = Char(exp_char); + return write_exponent(exp, it); + }; + auto usize = to_unsigned(size); + return specs.width > 0 + ? write_padded(out, specs, usize, write) + : base_iterator(out, write(reserve(out, usize))); +} + template FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f, const format_specs& specs, sign s, - locale_ref loc) -> OutputIt { + int exp_upper, locale_ref loc) -> OutputIt { if (is_constant_evaluated()) { - return do_write_float>(out, f, specs, s, loc); + return do_write_float>(out, f, specs, s, + exp_upper, loc); } else { - return do_write_float(out, f, specs, s, loc); + return do_write_float>(out, f, specs, s, + exp_upper, loc); } } @@ -2467,8 +2528,8 @@ template struct has_isfinite> : std::true_type {}; -template ::value&& - has_isfinite::value)> +template ::value&& has_isfinite::value)> FMT_CONSTEXPR20 auto isfinite(T value) -> bool { constexpr T inf = T(std::numeric_limits::infinity()); if (is_constant_evaluated()) @@ -2723,7 +2784,7 @@ class bigint { bigits_.resize(to_unsigned(num_bigits + exp_difference)); for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j) bigits_[j] = bigits_[i]; - memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit)); + fill_n(bigits_.data(), to_unsigned(exp_difference), 0U); exp_ -= exp_difference; } @@ -3284,9 +3345,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision, return exp; } -template -FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, - locale_ref loc) -> OutputIt { +template ::value)> +FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, + locale_ref loc = {}) -> OutputIt { + if (specs.localized() && write_loc(out, value, specs, loc)) return out; + // Use signbit because value < 0 is false for NaN. sign s = detail::signbit(value) ? sign::minus : specs.sign(); @@ -3299,15 +3363,15 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, if (specs.width != 0) --specs.width; } + const int exp_upper = detail::exp_upper(); int precision = specs.precision; if (precision < 0) { if (specs.type() != presentation_type::none) { precision = 6; } else if (is_fast_float::value && !is_constant_evaluated()) { // Use Dragonbox for the shortest format. - using floaty = conditional_t= sizeof(double), double, float>; - auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, s, loc); + auto dec = dragonbox::to_decimal(static_cast>(value)); + return write_float(out, dec, specs, s, exp_upper, loc); } } @@ -3335,16 +3399,7 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs, specs.precision = precision; auto f = big_decimal_fp{buffer.data(), static_cast(buffer.size()), exp}; - return write_float(out, f, specs, s, loc); -} - -template ::value)> -FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs, - locale_ref loc = {}) -> OutputIt { - return specs.localized() && write_loc(out, value, specs, loc) - ? out - : write_float(out, value, specs, loc); + return write_float(out, f, specs, s, exp_upper, loc); } template OutputIt { if (is_constant_evaluated()) return write(out, value, format_specs()); auto s = detail::signbit(value) ? sign::minus : sign::none; + auto mask = exponent_mask>(); + if ((bit_cast(value) & mask) == mask) + return write_nonfinite(out, std::isnan(value), {}, s); - constexpr auto specs = format_specs(); - using floaty = conditional_t= sizeof(double), double, float>; - using floaty_uint = typename dragonbox::float_info::carrier_uint; - floaty_uint mask = exponent_mask(); - if ((bit_cast(value) & mask) == mask) - return write_nonfinite(out, std::isnan(value), specs, s); + auto dec = dragonbox::to_decimal(static_cast>(value)); + int significand_size = count_digits(dec.significand); + int exp = dec.exponent + significand_size - 1; + if (use_fixed(exp, detail::exp_upper())) { + return write_fixed>( + out, dec, significand_size, Char('.'), {}, s); + } - auto dec = dragonbox::to_decimal(static_cast(value)); - return write_float(out, dec, specs, s, {}); + // Write value in the exponential format. + auto has_decimal_point = significand_size != 1; + size_t size = + to_unsigned((s != sign::none ? 1 : 0) + significand_size + + (has_decimal_point ? 1 : 0) + compute_exp_size(exp)); + auto it = reserve(out, size); + if (s != sign::none) *it++ = Char('-'); + // Insert a decimal point after the first digit and add an exponent. + it = write_significand(it, dec.significand, significand_size, 1, + has_decimal_point ? '.' : Char()); + *it++ = Char('e'); + it = write_exponent(exp, it); + return base_iterator(out, it); } template ::value && !is_fast_float::value)> inline auto write(OutputIt out, T value) -> OutputIt { - return write(out, value, format_specs()); + return write(out, value, {}); } template @@ -3639,6 +3709,12 @@ FMT_CONSTEXPR auto native_formatter::format( return write(ctx.out(), val, specs, ctx.locale()); } +// DEPRECATED! https://github.com/fmtlib/fmt/issues/4292. +template +struct is_locale : std::false_type {}; +template +struct is_locale> : std::true_type {}; + // DEPRECATED! template struct vformat_args { using type = basic_format_args>; @@ -3805,7 +3881,7 @@ struct formatter>> * auto s = fmt::format("{}", fmt::ptr(p)); */ template auto ptr(T p) -> const void* { - static_assert(std::is_pointer::value, ""); + static_assert(std::is_pointer::value, "fmt::ptr used with non-pointer"); return detail::bit_cast(p); } @@ -3830,13 +3906,14 @@ constexpr auto format_as(Enum e) noexcept -> underlying_t { } // namespace enums #ifdef __cpp_lib_byte -template <> struct formatter : formatter { +template +struct formatter : formatter { static auto format_as(std::byte b) -> unsigned char { return static_cast(b); } template auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) { - return formatter::format(format_as(b), ctx); + return formatter::format(format_as(b), ctx); } }; #endif @@ -3960,7 +4037,7 @@ template struct nested_formatter { write(basic_appender(buf)); auto specs = format_specs(); specs.width = width_; - specs.set_fill(specs_); + specs.copy_fill_from(specs_); specs.set_align(specs_.align()); return detail::write( ctx.out(), basic_string_view(buf.data(), buf.size()), specs); @@ -4120,41 +4197,46 @@ 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; -inline auto vformat(detail::locale_ref loc, string_view fmt, format_args args) +template ::value)> +inline auto vformat(const Locale& loc, string_view fmt, format_args args) -> std::string { auto buf = memory_buffer(); - detail::vformat_to(buf, fmt, args, loc); + detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); return {buf.data(), buf.size()}; } -template -FMT_INLINE auto format(detail::locale_ref loc, format_string fmt, - T&&... args) -> std::string { +template ::value)> +FMT_INLINE auto format(const Locale& loc, format_string fmt, T&&... args) + -> std::string { return vformat(loc, fmt.str, vargs{{args...}}); } -template ::value)> -auto vformat_to(OutputIt out, detail::locale_ref loc, string_view fmt, +auto vformat_to(OutputIt out, const Locale& loc, string_view fmt, format_args args) -> OutputIt { auto&& buf = detail::get_buffer(out); - detail::vformat_to(buf, fmt, args, loc); + detail::vformat_to(buf, fmt, args, detail::locale_ref(loc)); return detail::get_iterator(buf, out); } -template ::value)> -FMT_INLINE auto format_to(OutputIt out, detail::locale_ref loc, +template ::value&& + detail::is_locale::value)> +FMT_INLINE auto format_to(OutputIt out, const Locale& loc, format_string fmt, T&&... args) -> OutputIt { return fmt::vformat_to(out, loc, fmt.str, vargs{{args...}}); } -template -FMT_NODISCARD FMT_INLINE auto formatted_size(detail::locale_ref loc, +template ::value)> +FMT_NODISCARD FMT_INLINE auto formatted_size(const Locale& loc, format_string fmt, T&&... args) -> size_t { auto buf = detail::counting_buffer<>(); - detail::vformat_to(buf, fmt.str, vargs{{args...}}, loc); + detail::vformat_to(buf, fmt.str, vargs{{args...}}, + detail::locale_ref(loc)); return buf.count(); } @@ -4183,7 +4265,7 @@ FMT_NODISCARD FMT_INLINE auto format(format_string fmt, T&&... args) * std::string answer = fmt::to_string(42); */ template ::value)> -FMT_NODISCARD auto to_string(T value) -> std::string { +FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(T value) -> std::string { // The buffer should be large enough to store the number including the sign // or "false" for bool. char buffer[max_of(detail::digits10() + 2, 5)]; @@ -4191,13 +4273,15 @@ FMT_NODISCARD auto to_string(T value) -> std::string { } template ::value)> -FMT_NODISCARD auto to_string(const T& value) -> std::string { +FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value) + -> std::string { return to_string(format_as(value)); } template ::value && !detail::use_format_as::value)> -FMT_NODISCARD auto to_string(const T& value) -> std::string { +FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value) + -> std::string { auto buffer = memory_buffer(); detail::write(appender(buffer), value); return {buffer.data(), buffer.size()}; diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index 5d893c92..bf2371b7 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -33,8 +33,8 @@ FMT_BEGIN_NAMESPACE namespace detail { -// Generate a unique explicit instantion in every translation unit using a tag -// type in an anonymous namespace. +// Generate a unique explicit instantiation in every translation unit using a +// tag type in an anonymous namespace. namespace { struct file_access_tag {}; } // namespace @@ -150,7 +150,7 @@ inline void vprint(std::ostream& os, string_view fmt, format_args args) { FMT_EXPORT template void print(std::ostream& os, format_string fmt, T&&... args) { fmt::vargs vargs = {{args...}}; - if (detail::use_utf8) return vprint(os, fmt.str, vargs); + if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs); auto buffer = memory_buffer(); detail::vformat_to(buffer, fmt.str, vargs); detail::write_buffer(os, buffer); @@ -158,7 +158,8 @@ void print(std::ostream& os, format_string fmt, T&&... args) { FMT_EXPORT template void println(std::ostream& os, format_string fmt, T&&... args) { - fmt::print(os, "{}\n", fmt::format(fmt, std::forward(args)...)); + fmt::print(os, FMT_STRING("{}\n"), + fmt::format(fmt, std::forward(args)...)); } FMT_END_NAMESPACE diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 118d24fe..24c61e93 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -11,7 +11,6 @@ #ifndef FMT_MODULE # include # include -# include # include # include # include @@ -31,7 +30,7 @@ template class is_map { template static void check(...); public: - static constexpr const bool value = + static constexpr bool value = !std::is_void(nullptr))>::value; }; @@ -40,17 +39,16 @@ template class is_set { template static void check(...); public: - static constexpr const bool value = + static constexpr bool value = !std::is_void(nullptr))>::value && !is_map::value; }; // C array overload -template +template auto range_begin(const T (&arr)[N]) -> const T* { return arr; } -template -auto range_end(const T (&arr)[N]) -> const T* { +template auto range_end(const T (&arr)[N]) -> const T* { return arr + N; } @@ -120,7 +118,7 @@ template class is_tuple_like_ { template static void check(...); public: - static constexpr const bool value = + static constexpr bool value = !std::is_void(nullptr))>::value; }; @@ -154,7 +152,7 @@ using tuple_index_sequence = make_index_sequence::value>; template ::value> class is_tuple_formattable_ { public: - static constexpr const bool value = false; + static constexpr bool value = false; }; template class is_tuple_formattable_ { template @@ -170,7 +168,7 @@ template class is_tuple_formattable_ { C>::value)...>{})); public: - static constexpr const bool value = + static constexpr bool value = decltype(check(tuple_index_sequence{}))::value; }; @@ -208,7 +206,7 @@ template using result_t = std::tuple, Char>...>; using std::get; -template +template auto get_formatters(index_sequence) -> result_t(std::declval()))...>; } // namespace tuple @@ -219,7 +217,7 @@ template struct range_reference_type_impl { using type = decltype(*detail::range_begin(std::declval())); }; -template struct range_reference_type_impl { +template struct range_reference_type_impl { using type = T&; }; @@ -281,14 +279,15 @@ template struct format_tuple_element { } // namespace detail +FMT_EXPORT template struct is_tuple_like { - static constexpr const bool value = + static constexpr bool value = detail::is_tuple_like_::value && !detail::is_range_::value; }; +FMT_EXPORT template struct is_tuple_formattable { - static constexpr const bool value = - detail::is_tuple_formattable_::value; + static constexpr bool value = detail::is_tuple_formattable_::value; }; template @@ -343,8 +342,9 @@ struct formatter struct is_range { - static constexpr const bool value = + static constexpr bool value = detail::is_range_::value && !detail::has_to_string_view::value; }; @@ -368,6 +368,7 @@ template struct conjunction : conditional_t, P1> {}; +FMT_EXPORT template struct range_formatter; @@ -527,7 +528,9 @@ struct formatter< template struct formatter< R, Char, - enable_if_t::value == range_format::map>> { + enable_if_t::value == range_format::map>, + detail::is_formattable_delayed>::value>> { private: using map_type = detail::maybe_const_range; using element_type = detail::uncvref_type; @@ -668,7 +671,8 @@ struct formatter, Char> { } }; -template struct tuple_join_view : detail::view { +FMT_EXPORT +template struct tuple_join_view : detail::view { const Tuple& tuple; basic_string_view sep; @@ -683,15 +687,15 @@ 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::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::tuple_size()); } @@ -723,14 +727,14 @@ 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 { using std::get; @@ -752,7 +756,7 @@ template class is_container_adaptor_like { template static void check(...); public: - static constexpr const bool value = + static constexpr bool value = !std::is_void(nullptr))>::value; }; @@ -772,13 +776,13 @@ struct formatter< : formatter, Char> { using all = detail::all; template - auto format(const T& t, FormatContext& ctx) const -> decltype(ctx.out()) { + auto format(const T& value, FormatContext& ctx) const -> decltype(ctx.out()) { struct getter : T { - static auto get(const T& t) -> all { - return {t.*(&getter::c)}; // Access c through the derived class. + static auto get(const T& v) -> all { + return {v.*(&getter::c)}; // Access c through the derived class. } }; - return formatter::format(getter::get(t), ctx); + return formatter::format(getter::get(value), ctx); } }; @@ -823,7 +827,7 @@ auto join(Range&& r, string_view sep) */ template ::value)> FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep) - -> tuple_join_view { + -> tuple_join_view { return {tuple, sep}; } diff --git a/include/fmt/std.h b/include/fmt/std.h index bb07fedc..fff6da9d 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -15,15 +15,13 @@ # include # include # include -# include # include -# include +# include // std::reference_wrapper # include # include # include -# include -# include -# include +# include // std::type_info +# include // std::make_index_sequence // Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC. # if FMT_CPLUSPLUS >= 201703L @@ -79,11 +77,11 @@ # endif #endif -#if FMT_CPP_LIB_FILESYSTEM FMT_BEGIN_NAMESPACE - namespace detail { +#if FMT_CPP_LIB_FILESYSTEM + template auto get_path_string(const std::filesystem::path& p, const std::basic_string& native) { @@ -111,9 +109,169 @@ void write_escaped_path(basic_memory_buffer& quoted, } } +#endif // FMT_CPP_LIB_FILESYSTEM + +#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT +template +auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { + if constexpr (has_to_string_view::value) + return write_escaped_string(out, detail::to_string_view(v)); + if constexpr (std::is_same_v) return write_escaped_char(out, v); + return write(out, v); +} +#endif + +#if FMT_CPP_LIB_VARIANT + +template struct is_variant_like_ : std::false_type {}; +template +struct is_variant_like_> : std::true_type {}; + +template class is_variant_formattable { + template + static std::conjunction< + is_formattable, Char>...> + check(std::index_sequence); + + public: + static constexpr bool value = decltype(check( + std::make_index_sequence::value>()))::value; +}; + +#endif // FMT_CPP_LIB_VARIANT + +#if FMT_USE_RTTI + +template +auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt { +# ifdef FMT_HAS_ABI_CXA_DEMANGLE + int status = 0; + size_t size = 0; + std::unique_ptr demangled_name_ptr( + abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); + + string_view demangled_name_view; + if (demangled_name_ptr) { + demangled_name_view = demangled_name_ptr.get(); + + // Normalization of stdlib inline namespace names. + // libc++ inline namespaces. + // std::__1::* -> std::* + // std::__1::__fs::* -> std::* + // libstdc++ inline namespaces. + // std::__cxx11::* -> std::* + // std::filesystem::__cxx11::* -> std::filesystem::* + if (demangled_name_view.starts_with("std::")) { + char* begin = demangled_name_ptr.get(); + char* to = begin + 5; // std:: + for (char *from = to, *end = begin + demangled_name_view.size(); + from < end;) { + // This is safe, because demangled_name is NUL-terminated. + if (from[0] == '_' && from[1] == '_') { + char* next = from + 1; + while (next < end && *next != ':') next++; + if (next[0] == ':' && next[1] == ':') { + from = next + 2; + continue; + } + } + *to++ = *from++; + } + demangled_name_view = {begin, detail::to_unsigned(to - begin)}; + } + } else { + demangled_name_view = string_view(ti.name()); + } + return detail::write_bytes(out, demangled_name_view); +# elif FMT_MSC_VERSION + const string_view demangled_name(ti.name()); + for (size_t i = 0; i < demangled_name.size(); ++i) { + auto sub = demangled_name; + sub.remove_prefix(i); + if (sub.starts_with("enum ")) { + i += 4; + continue; + } + if (sub.starts_with("class ") || sub.starts_with("union ")) { + i += 5; + continue; + } + if (sub.starts_with("struct ")) { + i += 6; + continue; + } + if (*sub.begin() != ' ') *out++ = *sub.begin(); + } + return out; +# else + return detail::write_bytes(out, string_view(ti.name())); +# endif +} + +#endif // FMT_USE_RTTI + +template +struct has_flip : std::false_type {}; + +template +struct has_flip().flip())>> + : std::true_type {}; + +template struct is_bit_reference_like { + static constexpr bool value = std::is_convertible::value && + std::is_nothrow_assignable::value && + has_flip::value; +}; + +// Workaround for libc++ incompatibility with C++ standard. +// According to the Standard, `bitset::operator[] const` returns bool. +#ifdef _LIBCPP_VERSION +template +struct is_bit_reference_like> { + static constexpr bool value = true; +}; +#endif + +template +struct has_format_as : std::false_type {}; +template +struct has_format_as()))>> + : std::true_type {}; + +template +struct has_format_as_member : std::false_type {}; +template +struct has_format_as_member< + T, void_t::format_as(std::declval()))>> + : std::true_type {}; + } // namespace detail -FMT_EXPORT +template +auto ptr(const std::unique_ptr& p) -> const void* { + return p.get(); +} +template auto ptr(const std::shared_ptr& p) -> const void* { + return p.get(); +} + +#if FMT_CPP_LIB_FILESYSTEM + +class path : public std::filesystem::path { + public: + auto display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{}"), base); + } + auto system_string() const -> std::string { return string(); } + + auto generic_display_string() const -> std::string { + const std::filesystem::path& base = *this; + return fmt::format(FMT_STRING("{:g}"), base); + } + auto generic_system_string() const -> std::string { return generic_string(); } +}; + template struct formatter { private: format_specs specs_; @@ -163,40 +321,20 @@ template struct formatter { } }; -class path : public std::filesystem::path { - public: - auto display_string() const -> std::string { - const std::filesystem::path& base = *this; - return fmt::format(FMT_STRING("{}"), base); - } - auto system_string() const -> std::string { return string(); } - - auto generic_display_string() const -> std::string { - const std::filesystem::path& base = *this; - return fmt::format(FMT_STRING("{:g}"), base); - } - auto generic_system_string() const -> std::string { return generic_string(); } -}; - -FMT_END_NAMESPACE #endif // FMT_CPP_LIB_FILESYSTEM -FMT_BEGIN_NAMESPACE -FMT_EXPORT -template +template struct formatter, Char> : nested_formatter, Char> { private: - // Functor because C++11 doesn't support generic lambdas. + // This is a functor because C++11 doesn't support generic lambdas. struct writer { const std::bitset& bs; template FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt { - for (auto pos = N; pos > 0; --pos) { + for (auto pos = N; pos > 0; --pos) out = detail::write(out, bs[pos - 1] ? Char('1') : Char('0')); - } - return out; } }; @@ -209,14 +347,10 @@ struct formatter, Char> } }; -FMT_EXPORT template struct formatter : basic_ostream_formatter {}; -FMT_END_NAMESPACE #ifdef __cpp_lib_optional -FMT_BEGIN_NAMESPACE -FMT_EXPORT template struct formatter, Char, std::enable_if_t::value>> { @@ -255,31 +389,9 @@ struct formatter, Char, return detail::write(out, ')'); } }; -FMT_END_NAMESPACE #endif // __cpp_lib_optional -#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT - -FMT_BEGIN_NAMESPACE -namespace detail { - -template -auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { - if constexpr (has_to_string_view::value) - return write_escaped_string(out, detail::to_string_view(v)); - if constexpr (std::is_same_v) return write_escaped_char(out, v); - return write(out, v); -} - -} // namespace detail - -FMT_END_NAMESPACE -#endif - #ifdef __cpp_lib_expected -FMT_BEGIN_NAMESPACE - -FMT_EXPORT template struct formatter, Char, std::enable_if_t<(std::is_void::value || @@ -306,12 +418,9 @@ struct formatter, Char, return out; } }; -FMT_END_NAMESPACE #endif // __cpp_lib_expected #ifdef __cpp_lib_source_location -FMT_BEGIN_NAMESPACE -FMT_EXPORT template <> struct formatter { FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); } @@ -329,45 +438,14 @@ template <> struct formatter { return out; } }; -FMT_END_NAMESPACE #endif #if FMT_CPP_LIB_VARIANT -FMT_BEGIN_NAMESPACE -namespace detail { - -template -using variant_index_sequence = - std::make_index_sequence::value>; - -template struct is_variant_like_ : std::false_type {}; -template -struct is_variant_like_> : std::true_type {}; - -// formattable element check. -template class is_variant_formattable_ { - template - static std::conjunction< - is_formattable, C>...> - check(std::index_sequence); - - public: - static constexpr const bool value = - decltype(check(variant_index_sequence{}))::value; -}; - -} // namespace detail template struct is_variant_like { - static constexpr const bool value = detail::is_variant_like_::value; + static constexpr bool value = detail::is_variant_like_::value; }; -template struct is_variant_formattable { - static constexpr const bool value = - detail::is_variant_formattable_::value; -}; - -FMT_EXPORT template struct formatter { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); @@ -380,12 +458,11 @@ template struct formatter { } }; -FMT_EXPORT template -struct formatter< - Variant, Char, - std::enable_if_t, is_variant_formattable>>> { +struct formatter, + detail::is_variant_formattable>>> { FMT_CONSTEXPR auto parse(parse_context& ctx) -> const Char* { return ctx.begin(); } @@ -410,15 +487,14 @@ struct formatter< return out; } }; -FMT_END_NAMESPACE + #endif // FMT_CPP_LIB_VARIANT -FMT_BEGIN_NAMESPACE -FMT_EXPORT template <> struct formatter { private: format_specs specs_; detail::arg_ref width_ref_; + bool debug_ = false; public: FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { @@ -426,11 +502,19 @@ template <> struct formatter { if (it == end) return it; it = detail::parse_align(it, end, specs_); - if (it == end) return it; char c = *it; - if ((c >= '0' && c <= '9') || c == '{') + if (it != end && ((c >= '0' && c <= '9') || c == '{')) it = detail::parse_width(it, end, specs_, width_ref_, ctx); + + if (it != end && *it == '?') { + debug_ = true; + ++it; + } + if (it != end && *it == 's') { + specs_.set_type(presentation_type::string); + ++it; + } return it; } @@ -440,87 +524,25 @@ template <> struct formatter { auto specs = specs_; detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, ctx); - memory_buffer buf; - buf.append(string_view(ec.category().name())); - buf.push_back(':'); - detail::write(appender(buf), ec.value()); - return detail::write(ctx.out(), string_view(buf.data(), buf.size()), - specs); + auto buf = memory_buffer(); + if (specs_.type() == presentation_type::string) { + buf.append(ec.message()); + } else { + buf.append(string_view(ec.category().name())); + buf.push_back(':'); + detail::write(appender(buf), ec.value()); + } + auto quoted = memory_buffer(); + auto str = string_view(buf.data(), buf.size()); + if (debug_) { + detail::write_escaped_string(std::back_inserter(quoted), str); + str = string_view(quoted.data(), quoted.size()); + } + return detail::write(ctx.out(), str, specs); } }; #if FMT_USE_RTTI -namespace detail { - -template -auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt { -# ifdef FMT_HAS_ABI_CXA_DEMANGLE - int status = 0; - std::size_t size = 0; - std::unique_ptr demangled_name_ptr( - abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free); - - string_view demangled_name_view; - if (demangled_name_ptr) { - demangled_name_view = demangled_name_ptr.get(); - - // Normalization of stdlib inline namespace names. - // libc++ inline namespaces. - // std::__1::* -> std::* - // std::__1::__fs::* -> std::* - // libstdc++ inline namespaces. - // std::__cxx11::* -> std::* - // std::filesystem::__cxx11::* -> std::filesystem::* - if (demangled_name_view.starts_with("std::")) { - char* begin = demangled_name_ptr.get(); - char* to = begin + 5; // std:: - for (char *from = to, *end = begin + demangled_name_view.size(); - from < end;) { - // This is safe, because demangled_name is NUL-terminated. - if (from[0] == '_' && from[1] == '_') { - char* next = from + 1; - while (next < end && *next != ':') next++; - if (next[0] == ':' && next[1] == ':') { - from = next + 2; - continue; - } - } - *to++ = *from++; - } - demangled_name_view = {begin, detail::to_unsigned(to - begin)}; - } - } else { - demangled_name_view = string_view(ti.name()); - } - return detail::write_bytes(out, demangled_name_view); -# elif FMT_MSC_VERSION - const string_view demangled_name(ti.name()); - for (std::size_t i = 0; i < demangled_name.size(); ++i) { - auto sub = demangled_name; - sub.remove_prefix(i); - if (sub.starts_with("enum ")) { - i += 4; - continue; - } - if (sub.starts_with("class ") || sub.starts_with("union ")) { - i += 5; - continue; - } - if (sub.starts_with("struct ")) { - i += 6; - continue; - } - if (*sub.begin() != ' ') *out++ = *sub.begin(); - } - return out; -# else - return detail::write_bytes(out, string_view(ti.name())); -# endif -} - -} // namespace detail - -FMT_EXPORT template struct formatter { @@ -535,9 +557,8 @@ struct formatter(ctx.out(), ti); } }; -#endif +#endif // FMT_USE_RTTI -FMT_EXPORT template struct formatter< T, Char, // DEPRECATED! Mixing code unit types. @@ -572,38 +593,9 @@ struct formatter< } }; -namespace detail { - -template -struct has_flip : std::false_type {}; - -template -struct has_flip().flip())>> - : std::true_type {}; - -template struct is_bit_reference_like { - static constexpr const bool value = - std::is_convertible::value && - std::is_nothrow_assignable::value && has_flip::value; -}; - -#ifdef _LIBCPP_VERSION - -// Workaround for libc++ incompatibility with C++ standard. -// According to the Standard, `bitset::operator[] const` returns bool. -template -struct is_bit_reference_like> { - static constexpr const bool value = true; -}; - -#endif - -} // namespace detail - // We can't use std::vector::reference and // std::bitset::reference because the compiler can't deduce Allocator and N // in partial specialization. -FMT_EXPORT template struct formatter::value>> @@ -615,15 +607,6 @@ struct formatter -auto ptr(const std::unique_ptr& p) -> const void* { - return p.get(); -} -template auto ptr(const std::shared_ptr& p) -> const void* { - return p.get(); -} - -FMT_EXPORT template struct formatter, Char, enable_if_t::value>> @@ -636,7 +619,6 @@ struct formatter, Char, }; #ifdef __cpp_lib_atomic_flag_test -FMT_EXPORT template struct formatter : formatter { template @@ -647,7 +629,6 @@ struct formatter : formatter { }; #endif // __cpp_lib_atomic_flag_test -FMT_EXPORT template struct formatter, Char> { private: detail::dynamic_format_specs specs_; @@ -696,7 +677,7 @@ template struct formatter, Char> { auto outer_specs = format_specs(); outer_specs.width = specs.width; - outer_specs.set_fill(specs); + outer_specs.copy_fill_from(specs); outer_specs.set_align(specs.align()); specs.width = 0; @@ -710,10 +691,13 @@ template struct formatter, Char> { } }; -FMT_EXPORT template struct formatter, Char, - enable_if_t, Char>::value>> + // Guard against format_as because reference_wrapper is + // implicitly convertible to T&. + enable_if_t, Char>::value && + !detail::has_format_as::value && + !detail::has_format_as_member::value>> : formatter, Char> { template auto format(std::reference_wrapper ref, FormatContext& ctx) const @@ -723,4 +707,5 @@ struct formatter, Char, }; FMT_END_NAMESPACE + #endif // FMT_STD_H_ diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index 4cbda542..598fea75 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -112,10 +112,6 @@ inline auto runtime(wstring_view s) -> runtime_format_string { return {{s}}; } -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; - #ifdef __cpp_char8_t template <> struct is_char : bool_constant {}; #endif @@ -155,7 +151,7 @@ auto join(std::initializer_list list, wstring_view sep) template ::value)> auto join(const Tuple& tuple, basic_string_view sep) - -> tuple_join_view { + -> tuple_join_view { return {tuple, sep}; } @@ -191,9 +187,11 @@ auto format(const S& fmt, T&&... args) -> std::basic_string { fmt::make_format_args>(args...)); } -template , - FMT_ENABLE_IF(detail::is_exotic_char::value)> -inline auto vformat(detail::locale_ref loc, const S& fmt, +template , + FMT_ENABLE_IF(detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto vformat(const Locale& loc, const S& fmt, typename detail::vformat_args::type args) -> std::basic_string { auto buf = basic_memory_buffer(); @@ -202,10 +200,11 @@ inline auto vformat(detail::locale_ref loc, const S& fmt, return {buf.data(), buf.size()}; } -template , - FMT_ENABLE_IF(detail::is_exotic_char::value)> -inline auto format(detail::locale_ref loc, const S& fmt, T&&... args) + FMT_ENABLE_IF(detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto format(const Locale& loc, const S& fmt, T&&... args) -> std::basic_string { return vformat(loc, detail::to_string_view(fmt), fmt::make_format_args>(args...)); @@ -232,11 +231,12 @@ 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_exotic_char::value)> -inline auto vformat_to(OutputIt out, detail::locale_ref loc, const S& fmt, + detail::is_locale::value&& + detail::is_exotic_char::value)> +inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt, typename detail::vformat_args::type args) -> OutputIt { auto&& buf = detail::get_buffer(out); @@ -244,11 +244,12 @@ inline auto vformat_to(OutputIt out, detail::locale_ref 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, detail::locale_ref loc, const S& fmt, +inline auto format_to(OutputIt out, const Locale& loc, const S& fmt, T&&... args) -> typename std::enable_if::type { return vformat_to(out, loc, detail::to_string_view(fmt), @@ -317,7 +318,7 @@ template void println(wformat_string fmt, T&&... args) { return print(L"{}\n", fmt::format(fmt, std::forward(args)...)); } -inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) +inline auto vformat(text_style ts, wstring_view fmt, wformat_args args) -> std::wstring { auto buf = wmemory_buffer(); detail::vformat_to(buf, ts, fmt, args); @@ -325,19 +326,19 @@ inline auto vformat(const text_style& ts, wstring_view fmt, wformat_args args) } template -inline auto format(const text_style& ts, wformat_string fmt, T&&... args) +inline auto format(text_style ts, wformat_string fmt, T&&... args) -> std::wstring { return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); } template -FMT_DEPRECATED void print(std::FILE* f, const text_style& ts, - wformat_string fmt, const T&... args) { +FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string fmt, + const T&... args) { vprint(f, ts, fmt, fmt::make_wformat_args(args...)); } template -FMT_DEPRECATED void print(const text_style& ts, wformat_string fmt, +FMT_DEPRECATED void print(text_style ts, wformat_string fmt, const T&... args) { return print(stdout, ts, fmt, args...); } diff --git a/src/fmt.cc b/src/fmt.cc index 2dc4ef2f..671fd695 100644 --- a/src/fmt.cc +++ b/src/fmt.cc @@ -1,5 +1,7 @@ module; +#define FMT_MODULE + #ifdef _MSVC_LANG # define FMT_CPLUSPLUS _MSVC_LANG #else diff --git a/src/os.cc b/src/os.cc index c833a051..740e345d 100644 --- a/src/os.cc +++ b/src/os.cc @@ -66,14 +66,14 @@ using rwresult = int; // On Windows the count argument to read and write is unsigned, so convert // it from size_t preventing integer overflow. -inline unsigned convert_rwcount(std::size_t count) { +inline unsigned convert_rwcount(size_t count) { return count <= UINT_MAX ? static_cast(count) : UINT_MAX; } #elif FMT_USE_FCNTL // Return type of read and write functions. using rwresult = ssize_t; -inline std::size_t convert_rwcount(std::size_t count) { return count; } +inline size_t convert_rwcount(size_t count) { return count; } #endif } // namespace @@ -266,7 +266,7 @@ long long file::size() const { # endif } -std::size_t file::read(void* buffer, std::size_t count) { +size_t file::read(void* buffer, size_t count) { rwresult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count)))); if (result < 0) @@ -274,7 +274,7 @@ std::size_t file::read(void* buffer, std::size_t count) { return detail::to_unsigned(result); } -std::size_t file::write(const void* buffer, std::size_t count) { +size_t file::write(const void* buffer, size_t count) { rwresult result = 0; FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count)))); if (result < 0) diff --git a/support/bazel/.bazelversion b/support/bazel/.bazelversion index a8a18875..0e791524 100644 --- a/support/bazel/.bazelversion +++ b/support/bazel/.bazelversion @@ -1 +1 @@ -7.1.2 +8.1.1 diff --git a/support/bazel/BUILD.bazel b/support/bazel/BUILD.bazel index 1a06ed52..ebeac4ba 100644 --- a/support/bazel/BUILD.bazel +++ b/support/bazel/BUILD.bazel @@ -1,3 +1,5 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + cc_library( name = "fmt", srcs = [ diff --git a/support/bazel/MODULE.bazel b/support/bazel/MODULE.bazel index 6023f362..bb249751 100644 --- a/support/bazel/MODULE.bazel +++ b/support/bazel/MODULE.bazel @@ -3,4 +3,5 @@ module( compatibility_level = 10, ) -bazel_dep(name = "platforms", version = "0.0.10") +bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.1.1") diff --git a/support/bazel/README.md b/support/bazel/README.md index 44508f1c..795a6288 100644 --- a/support/bazel/README.md +++ b/support/bazel/README.md @@ -13,7 +13,7 @@ The [Bazel Central Registry](https://github.com/bazelbuild/bazel-central-registr For instance, to use {fmt} add to your `MODULE.bazel` file: ``` -bazel_dep(name = "fmt", version = "10.2.1") +bazel_dep(name = "fmt", version = "11.1.4") ``` ### Live at head diff --git a/support/mkdocs b/support/mkdocs index e554c1fd..610d81d6 100755 --- a/support/mkdocs +++ b/support/mkdocs @@ -63,7 +63,9 @@ if len(args) > 0: '--branch', 'master'], cwd=site_dir, env=env) if ret != 0 or version == 'dev': sys.exit(ret) - redirect_page_path = os.path.join(site_dir, version, 'api.html') + current_doc_path = os.path.join(site_dir, version) + os.makedirs(current_doc_path, exist_ok=True) + redirect_page_path = os.path.join(current_doc_path, 'api.html') with open(redirect_page_path, "w") as file: file.write(redirect_page) ret = call(['git', 'add', redirect_page_path], cwd=site_dir) diff --git a/test/base-test.cc b/test/base-test.cc index 52089fea..1de6a8eb 100644 --- a/test/base-test.cc +++ b/test/base-test.cc @@ -5,14 +5,16 @@ // // For the license information refer to format.h. +// Turn assertion failures into exceptions for testing. // clang-format off #include "test-assert.h" // clang-format on #include "fmt/base.h" -#include // INT_MAX -#include // std::strlen +#include // INT_MAX +#include // strlen + #include // std::equal_to #include // std::back_insert_iterator, std::distance #include // std::numeric_limits @@ -21,39 +23,36 @@ #include "gmock/gmock.h" -using fmt::string_view; -using fmt::detail::buffer; +#ifdef FMT_FORMAT_H_ +# error base-test includes format.h +#endif using testing::_; using testing::Invoke; using testing::Return; -#ifdef FMT_FORMAT_H_ -# error core-test includes format.h -#endif - -fmt::appender copy(fmt::string_view s, fmt::appender out) { +auto copy(fmt::string_view s, fmt::appender out) -> fmt::appender { for (char c : s) *out++ = c; return out; } TEST(string_view_test, value_type) { - static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); } TEST(string_view_test, ctor) { - EXPECT_STREQ("abc", fmt::string_view("abc").data()); - EXPECT_EQ(3u, fmt::string_view("abc").size()); + EXPECT_STREQ(fmt::string_view("abc").data(), "abc"); + EXPECT_EQ(fmt::string_view("abc").size(), 3u); - EXPECT_STREQ("defg", fmt::string_view(std::string("defg")).data()); - EXPECT_EQ(4u, fmt::string_view(std::string("defg")).size()); + EXPECT_STREQ(fmt::string_view(std::string("defg")).data(), "defg"); + EXPECT_EQ(fmt::string_view(std::string("defg")).size(), 4u); } TEST(string_view_test, length) { // Test that string_view::size() returns string length, not buffer size. char str[100] = "some string"; - EXPECT_EQ(std::strlen(str), string_view(str).size()); - EXPECT_LT(std::strlen(str), sizeof(str)); + EXPECT_EQ(fmt::string_view(str).size(), strlen(str)); + EXPECT_LT(strlen(str), sizeof(str)); } // Check string_view's comparison operator. @@ -62,13 +61,16 @@ template