Compare commits

..

No commits in common. "master" and "11.2.0" have entirely different histories.

61 changed files with 2386 additions and 3035 deletions

View File

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

View File

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

View File

@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Add Ubuntu mirrors - name: Add Ubuntu mirrors
run: | run: |

View File

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

View File

@ -55,7 +55,7 @@ jobs:
install: sudo apt install libc++-14-dev libc++abi-14-dev install: sudo apt install libc++-14-dev libc++abi-14-dev
steps: steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Set timezone - name: Set timezone
run: sudo timedatectl set-timezone 'Europe/Kyiv' run: sudo timedatectl set-timezone 'Europe/Kyiv'

View File

@ -9,10 +9,13 @@ jobs:
build: build:
strategy: strategy:
matrix: matrix:
os: [macos-14] os: [macos-13, macos-14]
build_type: [Debug, Release] build_type: [Debug, Release]
std: [11, 17, 20, 23] std: [11, 17, 20]
shared: [""] shared: [""]
exclude:
- { os: macos-13, std: 11 }
- { os: macos-13, std: 17 }
include: include:
- os: macos-14 - os: macos-14
std: 23 std: 23
@ -22,7 +25,7 @@ jobs:
runs-on: '${{ matrix.os }}' runs-on: '${{ matrix.os }}'
steps: steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Set timezone - name: Set timezone
run: sudo systemsetup -settimezone 'Europe/Minsk' run: sudo systemsetup -settimezone 'Europe/Minsk'

View File

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

View File

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

2
.gitignore vendored
View File

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

View File

@ -9,9 +9,7 @@ endif ()
# or if it is the master project. # or if it is the master project.
if (NOT DEFINED FMT_MASTER_PROJECT) if (NOT DEFINED FMT_MASTER_PROJECT)
set(FMT_MASTER_PROJECT OFF) set(FMT_MASTER_PROJECT OFF)
# NOTE: source vs current_source detection is unreliable if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
# this heuristic is more generally applicable esp w.r.t FetchContent
if (NOT DEFINED PROJECT_NAME)
set(FMT_MASTER_PROJECT ON) set(FMT_MASTER_PROJECT ON)
message(STATUS "CMake version: ${CMAKE_VERSION}") message(STATUS "CMake version: ${CMAKE_VERSION}")
endif () endif ()
@ -29,8 +27,12 @@ endfunction()
# DEPRECATED! Should be merged into add_module_library. # DEPRECATED! Should be merged into add_module_library.
function(enable_module target) function(enable_module target)
if (MSVC) if (MSVC)
if(NOT CMAKE_GENERATOR STREQUAL "Ninja") 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}") set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
endif()
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI) file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
target_compile_options(${target} target_compile_options(${target}
PRIVATE /interface /ifcOutput ${BMI} PRIVATE /interface /ifcOutput ${BMI}
@ -38,7 +40,6 @@ function(enable_module target)
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI}) set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
set_source_files_properties(${BMI} PROPERTIES GENERATED ON) set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
endif () endif ()
endif ()
endfunction() endfunction()
set(FMT_USE_CMAKE_MODULES FALSE) set(FMT_USE_CMAKE_MODULES FALSE)
@ -163,7 +164,7 @@ option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
# Options that control generation of various targets. # Options that control generation of various targets.
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT}) option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT}) option(FMT_INSTALL "Generate the install target." ON)
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT}) option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_FUZZ "Generate the fuzz target." OFF)
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
@ -430,7 +431,7 @@ if (FMT_INSTALL)
# Install the library and headers. # Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} install(TARGETS ${INSTALL_TARGETS}
COMPONENT fmt_core COMPONENT fmt-core
EXPORT ${targets_export_name} EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR} LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR}
@ -446,13 +447,13 @@ if (FMT_INSTALL)
# Install version, config and target files. # Install version, config and target files.
install(FILES ${project_config} ${version_config} install(FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR} DESTINATION ${FMT_CMAKE_DIR}
COMPONENT fmt_core) COMPONENT fmt-core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt:: NAMESPACE fmt::
COMPONENT fmt_core) COMPONENT fmt-core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}" install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT fmt_core) COMPONENT fmt-core)
endif () endif ()
function(add_doc_target) function(add_doc_target)
@ -489,7 +490,7 @@ function(add_doc_target)
include(GNUInstallDirs) include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/ install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT fmt_doc OPTIONAL) COMPONENT fmt-doc OPTIONAL)
endfunction() endfunction()
if (FMT_DOC) if (FMT_DOC)

View File

@ -1,250 +1,3 @@
# 12.1.0 - 2025-10-29
- Optimized `buffer::append`, resulting in up to ~16% improvement on spdlog
benchmarks (https://github.com/fmtlib/fmt/pull/4541). Thanks @fyrsta7.
- Worked around an ABI incompatibility in `std::locale_ref` between clang and
gcc (https://github.com/fmtlib/fmt/issues/4573).
- Made `std::variant` and `std::expected` formatters work with `format_as`
(https://github.com/fmtlib/fmt/issues/4574,
https://github.com/fmtlib/fmt/pull/4575). Thanks @phprus.
- Made `fmt::join<string_view>` work with C++ modules
(https://github.com/fmtlib/fmt/issues/4379,
https://github.com/fmtlib/fmt/pull/4577). Thanks @Arghnews.
- Exported `fmt::is_compiled_string` and `operator""_cf` from the module
(https://github.com/fmtlib/fmt/pull/4544). Thanks @CrackedMatter.
- Fixed a compatibility issue with C++ modules in clang
(https://github.com/fmtlib/fmt/pull/4548). Thanks @tsarn.
- Added support for cv-qualified types to the `std::optional` formatter
(https://github.com/fmtlib/fmt/issues/4561,
https://github.com/fmtlib/fmt/pull/4562). Thanks @OleksandrKvl.
- Added demangling support (used in exception and `std::type_info` formatters)
for libc++ and clang-cl
(https://github.com/fmtlib/fmt/issues/4542,
https://github.com/fmtlib/fmt/pull/4560,
https://github.com/fmtlib/fmt/issues/4568,
https://github.com/fmtlib/fmt/pull/4571).
Thanks @FatihBAKIR and @rohitsutreja.
- Switched to global `malloc`/`free` to enable allocator customization
(https://github.com/fmtlib/fmt/issues/4569,
https://github.com/fmtlib/fmt/pull/4570). Thanks @rohitsutreja.
- Made the `FMT_USE_CONSTEVAL` macro configurable by users
(https://github.com/fmtlib/fmt/pull/4546). Thanks @SnapperTT.
- Fixed compilation with locales disabled in the header-only mode
(https://github.com/fmtlib/fmt/issues/4550).
- Fixed compilation with clang 21 and `-std=c++20`
(https://github.com/fmtlib/fmt/issues/4552).
- Fixed a dynamic linking issue with clang-cl
(https://github.com/fmtlib/fmt/issues/4576,
https://github.com/fmtlib/fmt/pull/4584). Thanks @FatihBAKIR.
- Fixed a warning suppression leakage on gcc
(https://github.com/fmtlib/fmt/pull/4588). Thanks @ZedThree.
- Made more internal color APIs `constexpr`
(https://github.com/fmtlib/fmt/pull/4581). Thanks @ishani.
- Fixed compatibility with clang as a host compiler for NVCC
(https://github.com/fmtlib/fmt/pull/4564). Thanks @valgur.
- Fixed various warnings and lint issues
(https://github.com/fmtlib/fmt/issues/4565,
https://github.com/fmtlib/fmt/pull/4572,
https://github.com/fmtlib/fmt/pull/4557).
Thanks @LiangHuDream and @teruyamato0731.
- Improved documentation
(https://github.com/fmtlib/fmt/issues/4549,
https://github.com/fmtlib/fmt/pull/4551,
https://github.com/fmtlib/fmt/issues/4566,
https://github.com/fmtlib/fmt/pull/4567,
https://github.com/fmtlib/fmt/pull/4578,).
Thanks @teruyamato0731, @petersteneteg and @zimmerman-dev.
# 12.0.0 - 2025-09-17
- Optimized the default floating point formatting
(https://github.com/fmtlib/fmt/issues/3675,
https://github.com/fmtlib/fmt/issues/4516). In particular, formatting a
`double` with format string compilation into a stack allocated buffer is
more than 60% faster in version 12.0 compared to 11.2 according to
[dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark):
```
Function Time (ns) Speedup
fmt11 34.471 1.00x
fmt12 21.000 1.64x
```
<img width="766" height="609" src="https://github.com/user-attachments/assets/d7d768ad-7543-468c-b0bb-449abf73b31b" />
- Added `constexpr` support to `fmt::format`. For example:
```c++
#include <fmt/compile.h>
using namespace fmt::literals;
std::string s = fmt::format(""_cf, 42);
```
now works at compile time provided that `std::string` supports `constexpr`
(https://github.com/fmtlib/fmt/issues/3403,
https://github.com/fmtlib/fmt/pull/4456). Thanks @msvetkin.
- Added `FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time.
For example:
```c++
#include <fmt/compile.h>
constexpr auto s = FMT_STATIC_FORMAT("{}", 42);
```
compiles to just
```s
__ZL1s:
.asciiz "42"
```
It can be accessed as a C string with `s.c_str()` or as a string view with
`s.str()`.
- Improved C++20 module support
(https://github.com/fmtlib/fmt/pull/4451,
https://github.com/fmtlib/fmt/pull/4459,
https://github.com/fmtlib/fmt/pull/4476,
https://github.com/fmtlib/fmt/pull/4488,
https://github.com/fmtlib/fmt/issues/4491,
https://github.com/fmtlib/fmt/pull/4495).
Thanks @arBmind, @tkhyn, @Mishura4, @anonymouspc and @autoantwort.
- Switched to using estimated display width in precision. For example:
```c++
fmt::print("|{:.4}|\n|1234|\n", "🐱🐱🐱");
```
prints
![](https://github.com/user-attachments/assets/6c4446b3-13eb-43b9-b74a-b4543540ad6a)
because `🐱` has an estimated width of 2
(https://github.com/fmtlib/fmt/issues/4272,
https://github.com/fmtlib/fmt/pull/4443,
https://github.com/fmtlib/fmt/pull/4475).
Thanks @nikhilreddydev and @localspook.
- Fix interaction between debug presentation, precision, and width for strings
(https://github.com/fmtlib/fmt/pull/4478). Thanks @localspook.
- Implemented allocator propagation on `basic_memory_buffer` move
(https://github.com/fmtlib/fmt/issues/4487,
https://github.com/fmtlib/fmt/pull/4490). Thanks @toprakmurat.
- Fixed an ambiguity between `std::reference_wrapper<T>` and `format_as`
formatters (https://github.com/fmtlib/fmt/issues/4424,
https://github.com/fmtlib/fmt/pull/4434). Thanks @jeremy-rifkin.
- Removed the following deprecated APIs:
- `has_formatter`: use `is_formattable` instead,
- `basic_format_args::parse_context_type`,
`basic_format_args::formatter_type` and similar aliases in context types,
- wide stream overload of `fmt::printf`,
- wide stream overloads of `fmt::print` that take text styles,
- `is_*char` traits,
- `fmt::localtime`.
- Deprecated wide overloads of `fmt::fprintf` and `fmt::sprintf`.
- Improved diagnostics for the incorrect usage of `fmt::ptr`
(https://github.com/fmtlib/fmt/pull/4453). Thanks @TobiSchluter.
- Made handling of ANSI escape sequences more efficient
(https://github.com/fmtlib/fmt/pull/4511,
https://github.com/fmtlib/fmt/pull/4528).
Thanks @localspook and @Anas-Hamdane.
- Fixed a buffer overflow on all emphasis flags set
(https://github.com/fmtlib/fmt/pull/4498). Thanks @dominicpoeschko.
- Fixed an integer overflow for precision close to the max `int` value.
- Fixed compatibility with WASI (https://github.com/fmtlib/fmt/issues/4496,
https://github.com/fmtlib/fmt/pull/4497). Thanks @whitequark.
- Fixed `back_insert_iterator` detection, preventing a fallback on slower path
that handles arbitrary iterators (https://github.com/fmtlib/fmt/issues/4454).
- Fixed handling of invalid glibc `FILE` buffers
(https://github.com/fmtlib/fmt/issues/4469).
- Added `wchar_t` support to the `std::byte` formatter
(https://github.com/fmtlib/fmt/issues/4479,
https://github.com/fmtlib/fmt/pull/4480). Thanks @phprus.
- Changed component prefix from `fmt-` to `fmt_` for compatibility with
NSIS/CPack on Windows, e.g. `fmt-doc` changed to `fmt_doc`
(https://github.com/fmtlib/fmt/issues/4441,
https://github.com/fmtlib/fmt/pull/4442). Thanks @n-stein.
- Added the `FMT_CUSTOM_ASSERT_FAIL` macro to simplify providing a custom
`fmt::assert_fail` implementation (https://github.com/fmtlib/fmt/pull/4505).
Thanks @HazardyKnusperkeks.
- Switched to `FMT_THROW` on reporting format errors so that it can be
overriden by users when exceptions are disabled
(https://github.com/fmtlib/fmt/pull/4521). Thanks @HazardyKnusperkeks.
- Improved master project detection and disabled install targets when using
{fmt} as a subproject by default (https://github.com/fmtlib/fmt/pull/4536).
Thanks @crueter.
- Made various code improvements
(https://github.com/fmtlib/fmt/pull/4445,
https://github.com/fmtlib/fmt/pull/4448,
https://github.com/fmtlib/fmt/pull/4473,
https://github.com/fmtlib/fmt/pull/4522).
Thanks @localspook, @tchaikov and @way4sahil.
- Added Conan instructions to the docs
(https://github.com/fmtlib/fmt/pull/4537). Thanks @uilianries.
- Removed Bazel files to avoid issues with downstream packaging
(https://github.com/fmtlib/fmt/pull/4530). Thanks @mering.
- Added more entries for generated files to `.gitignore`
(https://github.com/fmtlib/fmt/pull/4355,
https://github.com/fmtlib/fmt/pull/4512).
Thanks @dinomight and @localspook.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/pull/4447,
https://github.com/fmtlib/fmt/issues/4470,
https://github.com/fmtlib/fmt/pull/4474,
https://github.com/fmtlib/fmt/pull/4477,
https://github.com/fmtlib/fmt/pull/4471,
https://github.com/fmtlib/fmt/pull/4483,
https://github.com/fmtlib/fmt/pull/4515,
https://github.com/fmtlib/fmt/issues/4533,
https://github.com/fmtlib/fmt/pull/4534).
Thanks @dodomorandi, @localspook, @remyjette, @Tomek-Stolarczyk, @Mishura4,
@mattiasljungstrom and @FatihBAKIR.
# 11.2.0 - 2025-05-03 # 11.2.0 - 2025-05-03
- Added the `s` specifier for `std::error_code`. It allows formatting an error - Added the `s` specifier for `std::error_code`. It allows formatting an error
@ -303,18 +56,17 @@
https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight. https://github.com/fmtlib/fmt/pull/4361). Thanks @dinomight.
- Added error reporting for duplicate named arguments - Added error reporting for duplicate named arguments
(https://github.com/fmtlib/fmt/issues/4282, (https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight.
https://github.com/fmtlib/fmt/pull/4367). Thanks @dinomight.
- Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0` - Fixed formatting of `long` with `FMT_BUILTIN_TYPES=0`
(https://github.com/fmtlib/fmt/issues/4375, (https://github.com/fmtlib/fmt/issues/4375,
https://github.com/fmtlib/fmt/issues/4394). https://github.com/fmtlib/fmt/issues/4394).
- Optimized `text_style` using bit packing - Optimized `text_style` using bit packing
(https://github.com/fmtlib/fmt/pull/4363). Thanks @localspook. (https://github.com/fmtlib/fmt/pull/4363). Thanks @LocalSpook.
- Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180, - Added support for incomplete types (https://github.com/fmtlib/fmt/issues/3180,
https://github.com/fmtlib/fmt/pull/4383). Thanks @localspook. https://github.com/fmtlib/fmt/pull/4383). Thanks @LocalSpook.
- Fixed a flush issue in `fmt::print` when using libstdc++ - Fixed a flush issue in `fmt::print` when using libstdc++
(https://github.com/fmtlib/fmt/issues/4398). (https://github.com/fmtlib/fmt/issues/4398).
@ -355,14 +107,13 @@
`float` (https://github.com/fmtlib/fmt/issues/3649). `float` (https://github.com/fmtlib/fmt/issues/3649).
- Moved `is_compiled_string` to the public API - Moved `is_compiled_string` to the public API
(https://github.com/fmtlib/fmt/issues/4335, (https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
https://github.com/fmtlib/fmt/issues/4342). Thanks @SwooshyCueb.
- Simplified implementation of `operator""_cf` - Simplified implementation of `operator""_cf`
(https://github.com/fmtlib/fmt/pull/4349). Thanks @localspook. (https://github.com/fmtlib/fmt/pull/4349). Thanks @LocalSpook.
- Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329). - Fixed `__builtin_strlen` detection (https://github.com/fmtlib/fmt/pull/4329).
Thanks @localspook. Thanks @LocalSpook.
- Fixed handling of BMI paths with the Ninja generator - Fixed handling of BMI paths with the Ninja generator
(https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn. (https://github.com/fmtlib/fmt/pull/4344). Thanks @tkhyn.

View File

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

View File

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

View File

@ -79,8 +79,6 @@ 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 There are two ways to make a user-defined type formattable: providing a
`format_as` function or specializing the `formatter` struct template. `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 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 type with the same format specifiers. The `format_as` function should
@ -415,11 +413,11 @@ locale:
that take `std::locale` as a parameter. The locale type is a template that take `std::locale` as a parameter. The locale type is a template
parameter to avoid the expensive `<locale>` include. parameter to avoid the expensive `<locale>` include.
::: format(locale_ref, format_string<T...>, T&&...) ::: format(const Locale&, format_string<T...>, T&&...)
::: format_to(OutputIt, locale_ref, format_string<T...>, T&&...) ::: format_to(OutputIt, const Locale&, format_string<T...>, T&&...)
::: formatted_size(locale_ref, format_string<T...>, T&&...) ::: formatted_size(const Locale&, format_string<T...>, T&&...)
<a id="legacy-checks"></a> <a id="legacy-checks"></a>
### Legacy Compile-Time Checks ### Legacy Compile-Time Checks
@ -549,62 +547,31 @@ fmt::print("{}", +s.bit);
This is a known limitation of "perfect" forwarding in C++. This is a known limitation of "perfect" forwarding in C++.
<a id="compile-api"></a> <a id="compile-api"></a>
## Compile-Time Support ## Format String Compilation
`fmt/compile.h` provides format string compilation and compile-time `fmt/compile.h` provides format string compilation and compile-time
(`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf` (`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf`
user-defined literal defined in namespace `fmt::literals`. Format strings user-defined literal defined in namespace `fmt::literals`. Format strings
marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into
efficient formatting code at compile-time. This supports arguments of built-in efficient formatting code at compile-time. This supports arguments of built-in
and string types as well as user-defined types with `format` methods taking and string types as well as user-defined types with `format` functions taking
the format context type as a template parameter in their `formatter` the format context type as a template parameter in their `formatter`
specializations. For example ([run](https://www.godbolt.org/z/3c13erEoq)): specializations. For example:
struct point {
double x;
double y;
};
template <> struct fmt::formatter<point> { template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); } constexpr auto parse(format_parse_context& ctx);
template <typename FormatContext> template <typename FormatContext>
auto format(const point& p, FormatContext& ctx) const { auto format(const point& p, FormatContext& ctx) const;
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
}; };
using namespace fmt::literals;
std::string s = fmt::format("{}"_cf, point(4, 2));
Format string compilation can generate more binary code compared to the Format string compilation can generate more binary code compared to the
default API and is only recommended in places where formatting is a default API and is only recommended in places where formatting is a
performance bottleneck. performance bottleneck.
The same APIs support formatting at compile time e.g. in `constexpr`
and `consteval` functions. Additionally there is an experimental
`FMT_STATIC_FORMAT` that allows formatting into a string of the exact
required size at compile time. Compile-time formatting works with built-in
and user-defined formatters that have `constexpr` `format` methods.
Example:
template <> struct fmt::formatter<point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
constexpr auto format(const point& p, FormatContext& ctx) const {
return format_to(ctx.out(), "({}, {})"_cf, p.x, p.y);
}
};
constexpr auto s = FMT_STATIC_FORMAT("{}", point(4, 2));
const char* cstr = s.c_str(); // Points the static string "(4, 2)".
::: operator""_cf
::: FMT_COMPILE ::: FMT_COMPILE
::: FMT_STATIC_FORMAT ::: operator""_cf
<a id="color-api"></a> <a id="color-api"></a>
## Terminal Colors and Text Styles ## Terminal Colors and Text Styles
@ -624,8 +591,6 @@ Example:
::: ostream ::: ostream
::: output_file(cstring_view, T...)
::: windows_error ::: windows_error
<a id="ostream-api"></a> <a id="ostream-api"></a>
@ -676,9 +641,9 @@ if an argument type doesn't match its format specification.
::: printf(string_view, const T&...) ::: printf(string_view, const T&...)
::: fprintf(std::FILE*, string_view, const T&...) ::: fprintf(std::FILE*, const S&, const T&...)
::: sprintf(string_view, const T&...) ::: sprintf(const S&, const T&...)
<a id="xchar-api"></a> <a id="xchar-api"></a>
## Wide Strings ## Wide Strings
@ -686,6 +651,8 @@ if an argument type doesn't match its format specification.
The optional header `fmt/xchar.h` provides support for `wchar_t` and The optional header `fmt/xchar.h` provides support for `wchar_t` and
exotic character types. exotic character types.
::: is_char
::: wstring_view ::: wstring_view
::: wformat_context ::: wformat_context
@ -708,55 +675,5 @@ following differences:
precision that provides round-trip guarantees similarly to other languages precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters `std::to_chars` which tries to generate the smallest number of characters
(ignoring redundant digits and sign in exponent) and may produce more (ignoring redundant digits and sign in exponent) and may procude more
decimal digits than necessary. decimal digits than necessary.
## Configuration Options
{fmt} provides configuration via CMake options and preprocessor macros to
enable or disable features and to optimize for binary size. For example, you
can disable OS-specific APIs defined in `fmt/os.h` with `-DFMT_OS=OFF` when
configuring CMake.
### CMake Options
- **`FMT_OS`**: When set to `OFF`, disables OS-specific APIs (`fmt/os.h`).
- **`FMT_UNICODE`**: When set of `OFF`, disables Unicode support on
Windows/MSVC. Unicode support is always enabled on other platforms.
### Macros
- **`FMT_HEADER_ONLY`**: Enables the header-only mode when defined. It is an
alternative to using the `fmt::fmt-header-only` CMake target.
Default: not defined.
- **`FMT_USE_EXCEPTIONS`**: Disables the use of exceptions when set to `0`.
Default: `1` (`0` if compiled with `-fno-exceptions`).
- **`FMT_USE_LOCALE`**: When set to `0`, disables locale support.
Default: `1` (`0` when `FMT_OPTIMIZE_SIZE > 1`).
- **`FMT_CUSTOM_ASSERT_FAIL`**: When set to `1`, allows users to provide a
custom `fmt::assert_fail` function which is called on assertion failures and,
if exceptions are disabled, on runtime errors. Default: `0`.
- **`FMT_BUILTIN_TYPES`**: When set to `0`, disables built-in handling of
arithmetic and string types other than `int`. This reduces library size at
the cost of per-call overhead. Default: `1`.
- **`FMT_OPTIMIZE_SIZE`**: Controls binary size optimizations:
- `0` - off (default)
- `1` - disables locale support and applies some optimizations
- `2` - disables some Unicode features, named arguments and applies more
aggressive optimizations
### Binary Size Optimization
To minimize the binary footprint of {fmt} as much as possible at the cost of
some features, you can use the following configuration:
- CMake options:
- `FMT_OS=OFF`
- Macros:
- `FMT_BUILTIN_TYPES=0`
- `FMT_OPTIMIZE_SIZE=2`

View File

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

View File

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

View File

@ -76,7 +76,7 @@ hide:
<p> <p>
The default is <b>locale-independent</b>, but you can opt into localized The default is <b>locale-independent</b>, but you can opt into localized
formatting and {fmt} makes it work with Unicode, addressing issues in the formatting and {fmt} makes it work with Unicode, addressing issues in the
standard library. standard libary.
</p> </p>
</div> </div>

View File

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

View File

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

View File

@ -21,7 +21,7 @@
#endif #endif
// The fmt library version in the form major * 10000 + minor * 100 + patch. // The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 120100 #define FMT_VERSION 110200
// Detect compiler versions. // Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__) #if defined(__clang__) && !defined(__ibmxl__)
@ -114,9 +114,7 @@
#endif #endif
// Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated. // Detect consteval, C++20 constexpr extensions and std::is_constant_evaluated.
#ifdef FMT_USE_CONSTEVAL #if !defined(__cpp_lib_is_constant_evaluated)
// Use the provided definition.
#elif !defined(__cpp_lib_is_constant_evaluated)
# define FMT_USE_CONSTEVAL 0 # define FMT_USE_CONSTEVAL 0
#elif FMT_CPLUSPLUS < 201709L #elif FMT_CPLUSPLUS < 201709L
# define FMT_USE_CONSTEVAL 0 # define FMT_USE_CONSTEVAL 0
@ -203,6 +201,14 @@
# define FMT_NODISCARD # define FMT_NODISCARD
#endif #endif
#ifdef FMT_DEPRECATED
// Use the provided definition.
#elif FMT_HAS_CPP14_ATTRIBUTE(deprecated)
# define FMT_DEPRECATED [[deprecated]]
#else
# define FMT_DEPRECATED /* deprecated */
#endif
#if FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_GCC_VERSION || FMT_CLANG_VERSION
# define FMT_VISIBILITY(value) __attribute__((visibility(value))) # define FMT_VISIBILITY(value) __attribute__((visibility(value)))
#else #else
@ -233,9 +239,9 @@
FMT_PRAGMA_GCC(push_options) FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) #if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og")) FMT_PRAGMA_GCC(optimize("Og"))
# define FMT_GCC_OPTIMIZED
#endif #endif
FMT_PRAGMA_CLANG(diagnostic push) FMT_PRAGMA_CLANG(diagnostic push)
FMT_PRAGMA_GCC(diagnostic push)
#ifdef FMT_ALWAYS_INLINE #ifdef FMT_ALWAYS_INLINE
// Use the provided definition. // Use the provided definition.
@ -245,7 +251,7 @@ FMT_PRAGMA_GCC(diagnostic push)
# define FMT_ALWAYS_INLINE inline # define FMT_ALWAYS_INLINE inline
#endif #endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. // A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#ifdef NDEBUG #if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
# define FMT_INLINE FMT_ALWAYS_INLINE # define FMT_INLINE FMT_ALWAYS_INLINE
#else #else
# define FMT_INLINE inline # define FMT_INLINE inline
@ -254,7 +260,7 @@ FMT_PRAGMA_GCC(diagnostic push)
#ifndef FMT_BEGIN_NAMESPACE #ifndef FMT_BEGIN_NAMESPACE
# define FMT_BEGIN_NAMESPACE \ # define FMT_BEGIN_NAMESPACE \
namespace fmt { \ namespace fmt { \
inline namespace v12 { inline namespace v11 {
# define FMT_END_NAMESPACE \ # define FMT_END_NAMESPACE \
} \ } \
} }
@ -350,9 +356,6 @@ template <typename T> constexpr auto max_of(T a, T b) -> T {
return a > b ? a : b; return a > b ? a : b;
} }
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message);
namespace detail { namespace detail {
// Suppresses "unused variable" warnings with the method described in // Suppresses "unused variable" warnings with the method described in
// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. // https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
@ -393,7 +396,7 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
# define FMT_ASSERT(condition, message) \ # define FMT_ASSERT(condition, message) \
((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
? (void)0 \ ? (void)0 \
: ::fmt::assert_fail(__FILE__, __LINE__, (message))) : fmt::detail::assert_fail(__FILE__, __LINE__, (message)))
#endif #endif
#ifdef FMT_USE_INT128 #ifdef FMT_USE_INT128
@ -416,12 +419,8 @@ inline auto map(int128_opt) -> monostate { return {}; }
inline auto map(uint128_opt) -> monostate { return {}; } inline auto map(uint128_opt) -> monostate { return {}; }
#endif #endif
#ifdef FMT_USE_BITINT #ifndef FMT_USE_BITINT
// Use the provided definition. # define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500)
#elif FMT_CLANG_VERSION >= 1500 && !defined(__CUDACC__)
# define FMT_USE_BITINT 1
#else
# define FMT_USE_BITINT 0
#endif #endif
#if FMT_USE_BITINT #if FMT_USE_BITINT
@ -464,13 +463,12 @@ enum { use_utf8 = !FMT_WIN32 || is_utf8_enabled };
static_assert(!FMT_UNICODE || use_utf8, static_assert(!FMT_UNICODE || use_utf8,
"Unicode support requires compiling with /utf-8"); "Unicode support requires compiling with /utf-8");
template <typename T> constexpr auto narrow(T*) -> char* { return nullptr; } template <typename T> constexpr const char* narrow(const T*) { return nullptr; }
constexpr FMT_ALWAYS_INLINE auto narrow(const char* s) -> const char* { constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; }
return s;
}
template <typename Char> template <typename Char>
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int { FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n)
-> int {
if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n); if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);
for (; n != 0; ++s1, ++s2, --n) { for (; n != 0; ++s1, ++s2, --n) {
if (*s1 < *s2) return -1; if (*s1 < *s2) return -1;
@ -542,7 +540,7 @@ template <typename Char> class basic_string_view {
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) { FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION #if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) { if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) {
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr. size_ = __builtin_strlen(detail::narrow(s)); // strlen is not costexpr.
return; return;
} }
#endif #endif
@ -618,6 +616,19 @@ template <typename Char> class basic_string_view {
using string_view = basic_string_view<char>; using string_view = basic_string_view<char>;
// DEPRECATED! Will be merged with is_char and moved to detail.
template <typename T> struct is_xchar : std::false_type {};
template <> struct is_xchar<wchar_t> : std::true_type {};
template <> struct is_xchar<char16_t> : std::true_type {};
template <> struct is_xchar<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_xchar<char8_t> : std::true_type {};
#endif
// Specifies if `T` is a character (code unit) type.
template <typename T> struct is_char : is_xchar<T> {};
template <> struct is_char<char> : std::true_type {};
template <typename T> class basic_appender; template <typename T> class basic_appender;
using appender = basic_appender<char>; using appender = basic_appender<char>;
@ -770,7 +781,7 @@ class basic_specs {
(static_cast<unsigned>(p) << precision_shift); (static_cast<unsigned>(p) << precision_shift);
} }
constexpr auto dynamic() const -> bool { constexpr bool dynamic() const {
return (data_ & (width_mask | precision_mask)) != 0; return (data_ & (width_mask | precision_mask)) != 0;
} }
@ -910,50 +921,14 @@ template <typename Char = char> class parse_context {
FMT_CONSTEXPR void check_dynamic_spec(int arg_id); FMT_CONSTEXPR void check_dynamic_spec(int arg_id);
}; };
#ifndef FMT_USE_LOCALE
# define FMT_USE_LOCALE (FMT_OPTIMIZE_SIZE <= 1)
#endif
// A type-erased reference to std::locale to avoid the heavy <locale> include.
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc) : locale_(&loc) {
// Check if std::isalpha is found via ADL to reduce the chance of misuse.
detail::ignore_unused(sizeof(isalpha('x', loc)));
}
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
FMT_END_EXPORT FMT_END_EXPORT
namespace detail { namespace detail {
// Specifies if `T` is a code unit type.
template <typename T> struct is_code_unit : std::false_type {};
template <> struct is_code_unit<char> : std::true_type {};
template <> struct is_code_unit<wchar_t> : std::true_type {};
template <> struct is_code_unit<char16_t> : std::true_type {};
template <> struct is_code_unit<char32_t> : std::true_type {};
#ifdef __cpp_char8_t
template <> struct is_code_unit<char8_t> : bool_constant<is_utf8_enabled> {};
#endif
// Constructs fmt::basic_string_view<Char> from types implicitly convertible // Constructs fmt::basic_string_view<Char> from types implicitly convertible
// to it, deducing Char. Explicitly convertible types such as the ones returned // to it, deducing Char. Explicitly convertible types such as the ones returned
// from FMT_STRING are intentionally excluded. // from FMT_STRING are intentionally excluded.
template <typename Char, FMT_ENABLE_IF(is_code_unit<Char>::value)> template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)>
constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> { constexpr auto to_string_view(const Char* s) -> basic_string_view<Char> {
return s; return s;
} }
@ -1082,11 +1057,11 @@ template <bool B1, bool B2, bool... Tail> constexpr auto count() -> int {
return (B1 ? 1 : 0) + count<B2, Tail...>(); return (B1 ? 1 : 0) + count<B2, Tail...>();
} }
template <typename... T> constexpr auto count_named_args() -> int { template <typename... Args> constexpr auto count_named_args() -> int {
return count<is_named_arg<T>::value...>(); return count<is_named_arg<Args>::value...>();
} }
template <typename... T> constexpr auto count_static_named_args() -> int { template <typename... Args> constexpr auto count_static_named_args() -> int {
return count<is_static_named_arg<T>::value...>(); return count<is_static_named_arg<Args>::value...>();
} }
template <typename Char> struct named_arg_info { template <typename Char> struct named_arg_info {
@ -1094,7 +1069,7 @@ template <typename Char> struct named_arg_info {
int id; int id;
}; };
// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13. // named_args is non-const to suppress a bogus -Wmaybe-uninitalized in gcc 13.
template <typename Char> template <typename Char>
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args, FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
int named_arg_index, int named_arg_index,
@ -1198,7 +1173,7 @@ template <typename Char> struct type_mapper {
static auto map(ubitint<N>) static auto map(ubitint<N>)
-> conditional_t<N <= 64, unsigned long long, void>; -> conditional_t<N <= 64, unsigned long long, void>;
template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)> template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
static auto map(T) -> conditional_t< static auto map(T) -> conditional_t<
std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>; std::is_same<T, char>::value || std::is_same<T, Char>::value, Char, void>;
@ -1704,12 +1679,12 @@ template <typename... T> struct arg_pack {};
template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES> template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>
class format_string_checker { class format_string_checker {
private: private:
type types_[max_of<size_t>(1, NUM_ARGS)]; type types_[max_of(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of<size_t>(1, NUM_NAMED_ARGS)]; named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)];
compile_parse_context<Char> context_; compile_parse_context<Char> context_;
using parse_func = auto (*)(parse_context<Char>&) -> const Char*; using parse_func = auto (*)(parse_context<Char>&) -> const Char*;
parse_func parse_funcs_[max_of<size_t>(1, NUM_ARGS)]; parse_func parse_funcs_[max_of(1, NUM_ARGS)];
public: public:
template <typename... T> template <typename... T>
@ -1850,19 +1825,15 @@ template <typename T> class buffer {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940
FMT_CONSTEXPR20 FMT_CONSTEXPR20
#endif #endif
void append(const U* begin, const U* end) { void
append(const U* begin, const U* end) {
while (begin != end) { while (begin != end) {
auto size = size_;
auto free_cap = capacity_ - size;
auto count = to_unsigned(end - begin); auto count = to_unsigned(end - begin);
if (free_cap < count) { try_reserve(size_ + count);
grow_(*this, size + count); auto free_cap = capacity_ - size_;
size = size_; if (free_cap < count) count = free_cap;
free_cap = capacity_ - size;
count = count < free_cap ? count : free_cap;
}
// A loop is faster than memcpy on small sizes. // A loop is faster than memcpy on small sizes.
T* out = ptr_ + size; T* out = ptr_ + size_;
for (size_t i = 0; i < count; ++i) out[i] = begin[i]; for (size_t i = 0; i < count; ++i) out[i] = begin[i];
size_ += count; size_ += count;
begin += count; begin += count;
@ -2062,17 +2033,6 @@ struct has_back_insert_iterator_container_append<
.append(std::declval<InputIt>(), .append(std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {}; std::declval<InputIt>()))>> : std::true_type {};
template <typename OutputIt, typename InputIt, typename = void>
struct has_back_insert_iterator_container_insert_at_end : std::false_type {};
template <typename OutputIt, typename InputIt>
struct has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt,
void_t<decltype(get_container(std::declval<OutputIt>())
.insert(get_container(std::declval<OutputIt>()).end(),
std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
// An optimized version of std::copy with the output value type (T). // An optimized version of std::copy with the output value type (T).
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&& FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&
@ -2087,8 +2047,6 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value && FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
!has_back_insert_iterator_container_append< !has_back_insert_iterator_container_append<
OutputIt, InputIt>::value &&
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)> OutputIt, InputIt>::value)>
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out) FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
-> OutputIt { -> OutputIt {
@ -2098,11 +2056,7 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
} }
template <typename T, typename InputIt, typename OutputIt, template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(!(is_back_insert_iterator<OutputIt>::value && FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)>
(has_back_insert_iterator_container_append<
OutputIt, InputIt>::value ||
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)))>
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
while (begin != end) *out++ = static_cast<T>(*begin++); while (begin != end) *out++ = static_cast<T>(*begin++);
return out; return out;
@ -2222,7 +2176,7 @@ template <typename Context> class value {
static_assert(N <= 64, "unsupported _BitInt"); static_assert(N <= 64, "unsupported _BitInt");
} }
template <typename T, FMT_ENABLE_IF(is_code_unit<T>::value)> template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) { constexpr FMT_INLINE value(T x FMT_BUILTIN) : char_value(x) {
static_assert( static_assert(
std::is_same<T, char>::value || std::is_same<T, char_type>::value, std::is_same<T, char>::value || std::is_same<T, char_type>::value,
@ -2298,7 +2252,7 @@ template <typename Context> class value {
custom.value = const_cast<value_type*>(&x); custom.value = const_cast<value_type*>(&x);
#endif #endif
} }
custom.format = format_custom<value_type>; custom.format = format_custom<value_type, formatter<value_type, char_type>>;
} }
template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())> template <typename T, FMT_ENABLE_IF(!has_formatter<T, char_type>())>
@ -2309,10 +2263,10 @@ template <typename Context> class value {
} }
// Formats an argument of a custom type, such as a user-defined class. // Formats an argument of a custom type, such as a user-defined class.
template <typename T> template <typename T, typename Formatter>
static void format_custom(void* arg, parse_context<char_type>& parse_ctx, static void format_custom(void* arg, parse_context<char_type>& parse_ctx,
Context& ctx) { Context& ctx) {
auto f = formatter<T, char_type>(); auto f = Formatter();
parse_ctx.advance_to(f.parse(parse_ctx)); parse_ctx.advance_to(f.parse(parse_ctx));
using qualified_type = using qualified_type =
conditional_t<has_formatter<const T, char_type>(), const T, T>; conditional_t<has_formatter<const T, char_type>(), const T, T>;
@ -2339,14 +2293,35 @@ struct is_output_iterator<
enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++), enable_if_t<std::is_assignable<decltype(*std::declval<decay_t<It>&>()++),
T>::value>> : std::true_type {}; 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 <locale> include.
class locale_ref {
#if FMT_USE_LOCALE
private:
const void* locale_; // A type-erased pointer to std::locale.
public:
constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
public:
template <typename Locale> auto get() const -> Locale;
};
template <typename> constexpr auto encode_types() -> unsigned long long { template <typename> constexpr auto encode_types() -> unsigned long long {
return 0; return 0;
} }
template <typename Context, typename First, typename... T> template <typename Context, typename Arg, typename... Args>
constexpr auto encode_types() -> unsigned long long { constexpr auto encode_types() -> unsigned long long {
return static_cast<unsigned>(stored_type_constant<First, Context>::value) | return static_cast<unsigned>(stored_type_constant<Arg, Context>::value) |
(encode_types<Context, T...>() << packed_arg_bits); (encode_types<Context, Args...>() << packed_arg_bits);
} }
template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)> template <typename Context, typename... T, size_t NUM_ARGS = sizeof...(T)>
@ -2363,9 +2338,8 @@ template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
unsigned long long DESC> unsigned long long DESC>
struct named_arg_store { struct named_arg_store {
// args_[0].named_args points to named_args to avoid bloating format_args. // args_[0].named_args points to named_args to avoid bloating format_args.
arg_t<Context, NUM_ARGS> args[1u + NUM_ARGS]; arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS];
named_arg_info<typename Context::char_type> named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS];
named_args[static_cast<size_t>(NUM_NAMED_ARGS)];
template <typename... T> template <typename... T>
FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values) FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)
@ -2384,8 +2358,8 @@ struct named_arg_store {
} }
named_arg_store(const named_arg_store& rhs) = delete; named_arg_store(const named_arg_store& rhs) = delete;
auto operator=(const named_arg_store& rhs) -> named_arg_store& = delete; named_arg_store& operator=(const named_arg_store& rhs) = delete;
auto operator=(named_arg_store&& rhs) -> named_arg_store& = delete; named_arg_store& operator=(named_arg_store&& rhs) = delete;
operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; } operator const arg_t<Context, NUM_ARGS>*() const { return args + 1; }
}; };
@ -2398,7 +2372,7 @@ struct format_arg_store {
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. // +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
using type = using type =
conditional_t<NUM_NAMED_ARGS == 0, conditional_t<NUM_NAMED_ARGS == 0,
arg_t<Context, NUM_ARGS>[max_of<size_t>(1, NUM_ARGS)], arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)],
named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>; named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;
type args; type args;
}; };
@ -2682,17 +2656,22 @@ class context {
private: private:
appender out_; appender out_;
format_args args_; format_args args_;
FMT_NO_UNIQUE_ADDRESS locale_ref loc_; FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_;
public: public:
using char_type = char; ///< The character type for the output. /// The character type for the output.
using char_type = char;
using iterator = appender; using iterator = appender;
using format_arg = basic_format_arg<context>; using format_arg = basic_format_arg<context>;
using parse_context_type FMT_DEPRECATED = parse_context<>;
template <typename T> using formatter_type FMT_DEPRECATED = formatter<T>;
enum { builtin_types = FMT_BUILTIN_TYPES }; enum { builtin_types = FMT_BUILTIN_TYPES };
/// Constructs a `context` object. References to the arguments are stored /// Constructs a `context` object. References to the arguments are stored
/// in the object so make sure they have appropriate lifetimes. /// in the object so make sure they have appropriate lifetimes.
FMT_CONSTEXPR context(iterator out, format_args args, locale_ref loc = {}) FMT_CONSTEXPR context(iterator out, format_args args,
detail::locale_ref loc = {})
: out_(out), args_(args), loc_(loc) {} : out_(out), args_(args), loc_(loc) {}
context(context&&) = default; context(context&&) = default;
context(const context&) = delete; context(const context&) = delete;
@ -2713,7 +2692,7 @@ class context {
// Advances the begin iterator to `it`. // Advances the begin iterator to `it`.
FMT_CONSTEXPR void advance_to(iterator) {} FMT_CONSTEXPR void advance_to(iterator) {}
FMT_CONSTEXPR auto locale() const -> locale_ref { return loc_; } FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; }
}; };
template <typename Char = char> struct runtime_format_string { template <typename Char = char> struct runtime_format_string {
@ -2754,9 +2733,7 @@ template <typename... T> struct fstring {
static_assert(count<(is_view<remove_cvref_t<T>>::value && static_assert(count<(is_view<remove_cvref_t<T>>::value &&
std::is_reference<T>::value)...>() == 0, std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed"); "passing views as lvalues is disallowed");
#if FMT_USE_CONSTEVAL if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
parse_format_string<char>(s, checker(s, arg_pack()));
#endif
#ifdef FMT_ENFORCE_COMPILE_STRING #ifdef FMT_ENFORCE_COMPILE_STRING
static_assert( static_assert(
FMT_USE_CONSTEVAL && sizeof(s) != 0, FMT_USE_CONSTEVAL && sizeof(s) != 0,
@ -2802,6 +2779,9 @@ template <typename T, typename Char = char>
concept formattable = is_formattable<remove_reference_t<T>, Char>::value; concept formattable = is_formattable<remove_reference_t<T>, Char>::value;
#endif #endif
template <typename T, typename Char>
using has_formatter FMT_DEPRECATED = std::is_constructible<formatter<T, Char>>;
// A formatter specialization for natively supported types. // A formatter specialization for natively supported types.
template <typename T, typename Char> template <typename T, typename Char>
struct formatter<T, Char, struct formatter<T, Char,
@ -2998,10 +2978,9 @@ FMT_INLINE void println(format_string<T...> fmt, T&&... args) {
return fmt::println(stdout, fmt, static_cast<T&&>(args)...); return fmt::println(stdout, fmt, static_cast<T&&>(args)...);
} }
FMT_PRAGMA_GCC(diagnostic pop) FMT_END_EXPORT
FMT_PRAGMA_CLANG(diagnostic pop) FMT_PRAGMA_CLANG(diagnostic pop)
FMT_PRAGMA_GCC(pop_options) FMT_PRAGMA_GCC(pop_options)
FMT_END_EXPORT
FMT_END_NAMESPACE FMT_END_NAMESPACE
#ifdef FMT_HEADER_ONLY #ifdef FMT_HEADER_ONLY

View File

@ -38,7 +38,6 @@ FMT_BEGIN_NAMESPACE
// Copyright Paul Dreik 2019 // Copyright Paul Dreik 2019
namespace safe_duration_cast { namespace safe_duration_cast {
// DEPRECATED!
template <typename To, typename From, template <typename To, typename From,
FMT_ENABLE_IF(!std::is_same<From, To>::value && FMT_ENABLE_IF(!std::is_same<From, To>::value &&
std::numeric_limits<From>::is_signed == std::numeric_limits<From>::is_signed ==
@ -162,6 +161,17 @@ auto safe_duration_cast(std::chrono::duration<FromRep, FromPeriod> from,
int& ec) -> To { int& ec) -> To {
using From = std::chrono::duration<FromRep, FromPeriod>; using From = std::chrono::duration<FromRep, FromPeriod>;
ec = 0; ec = 0;
if (std::isnan(from.count())) {
// nan in, gives nan out. easy.
return To{std::numeric_limits<typename To::rep>::quiet_NaN()};
}
// maybe we should also check if from is denormal, and decide what to do about
// it.
// +-inf should be preserved.
if (std::isinf(from.count())) {
return To{from.count()};
}
// the basic idea is that we need to convert from count() in the from type // the basic idea is that we need to convert from count() in the from type
// to count() in the To type, by multiplying it with this: // to count() in the To type, by multiplying it with this:
@ -272,6 +282,8 @@ namespace detail {
#define FMT_NOMACRO #define FMT_NOMACRO
template <typename T = void> struct null {}; template <typename T = void> struct null {};
inline auto localtime_r FMT_NOMACRO(...) -> null<> { return null<>(); }
inline auto localtime_s(...) -> null<> { return null<>(); }
inline auto gmtime_r(...) -> null<> { return null<>(); } inline auto gmtime_r(...) -> null<> { return null<>(); }
inline auto gmtime_s(...) -> null<> { return null<>(); } inline auto gmtime_s(...) -> null<> { return null<>(); }
@ -314,7 +326,7 @@ inline auto get_classic_locale() -> const std::locale& {
} }
template <typename CodeUnit> struct codecvt_result { template <typename CodeUnit> struct codecvt_result {
static constexpr size_t max_size = 32; static constexpr const size_t max_size = 32;
CodeUnit buf[max_size]; CodeUnit buf[max_size];
CodeUnit* end; CodeUnit* end;
}; };
@ -431,7 +443,11 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
using common_rep = typename std::common_type<FromRep, typename To::rep, using common_rep = typename std::common_type<FromRep, typename To::rep,
decltype(factor::num)>::type; decltype(factor::num)>::type;
common_rep count = from.count(); // This conversion is lossless.
int ec = 0;
auto count = safe_duration_cast::lossless_integral_conversion<common_rep>(
from.count(), ec);
if (ec) throw_duration_error();
// Multiply from.count() by factor and check for overflow. // Multiply from.count() by factor and check for overflow.
if (const_check(factor::num != 1)) { if (const_check(factor::num != 1)) {
@ -442,7 +458,6 @@ auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
count *= factor::num; count *= factor::num;
} }
if (const_check(factor::den != 1)) count /= factor::den; if (const_check(factor::den != 1)) count /= factor::den;
int ec = 0;
auto to = auto to =
To(safe_duration_cast::lossless_integral_conversion<typename To::rep>( To(safe_duration_cast::lossless_integral_conversion<typename To::rep>(
count, ec)); count, ec));
@ -456,8 +471,6 @@ template <typename To, typename FromRep, typename FromPeriod,
std::is_floating_point<typename To::rep>::value)> std::is_floating_point<typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
#if FMT_SAFE_DURATION_CAST #if FMT_SAFE_DURATION_CAST
// Preserve infinity and NaN.
if (!isfinite(from.count())) return static_cast<To>(from.count());
// Throwing version of safe_duration_cast is only available for // Throwing version of safe_duration_cast is only available for
// integer to integer or float to float casts. // integer to integer or float to float casts.
int ec; int ec;
@ -474,7 +487,7 @@ template <typename To, typename FromRep, typename FromPeriod,
FMT_ENABLE_IF( FMT_ENABLE_IF(
!is_similar_arithmetic_type<FromRep, typename To::rep>::value)> !is_similar_arithmetic_type<FromRep, typename To::rep>::value)>
auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To { auto duration_cast(std::chrono::duration<FromRep, FromPeriod> from) -> To {
// Mixed integer <-> float cast is not supported by safe duration_cast. // Mixed integer <-> float cast is not supported by safe_duration_cast.
return std::chrono::duration_cast<To>(from); return std::chrono::duration_cast<To>(from);
} }
@ -488,10 +501,86 @@ auto to_time_t(sys_time<Duration> time_point) -> std::time_t {
.count(); .count();
} }
namespace tz {
// DEPRECATED!
struct time_zone {
template <typename Duration, typename LocalTime>
auto to_sys(LocalTime) -> sys_time<Duration> {
return {};
}
};
template <typename... T> auto current_zone(T...) -> time_zone* {
return nullptr;
}
template <typename... T> void _tzset(T...) {}
} // namespace tz
// DEPRECATED!
inline void tzset_once() {
static bool init = []() {
using namespace tz;
_tzset();
return false;
}();
ignore_unused(init);
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
/**
* Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in local time. Unlike `std::localtime`, this function is
* thread-safe on most platforms.
*/
FMT_DEPRECATED inline auto localtime(std::time_t time) -> std::tm {
struct dispatcher {
std::time_t time_;
std::tm tm_;
inline dispatcher(std::time_t t) : time_(t) {}
inline auto run() -> bool {
using namespace fmt::detail;
return handle(localtime_r(&time_, &tm_));
}
inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }
inline auto handle(detail::null<>) -> bool {
using namespace fmt::detail;
return fallback(localtime_s(&tm_, &time_));
}
inline auto fallback(int res) -> bool { return res == 0; }
#if !FMT_MSC_VERSION
inline auto fallback(detail::null<>) -> bool {
using namespace fmt::detail;
std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm;
return tm != nullptr;
}
#endif
};
dispatcher lt(time);
// Too big time values may be unsupported.
if (!lt.run()) FMT_THROW(format_error("time_t value out of range"));
return lt.tm_;
}
#if FMT_USE_LOCAL_TIME
template <typename Duration>
FMT_DEPRECATED auto localtime(std::chrono::local_time<Duration> time)
-> std::tm {
using namespace std::chrono;
using namespace detail::tz;
return localtime(detail::to_time_t(current_zone()->to_sys<Duration>(time)));
}
#endif
/** /**
* Converts given time since epoch as `std::time_t` value into calendar time, * Converts given time since epoch as `std::time_t` value into calendar time,
* expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this * expressed in Coordinated Universal Time (UTC). Unlike `std::gmtime`, this
@ -563,7 +652,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
// Add ASCII '0' to each digit byte and insert separators. // Add ASCII '0' to each digit byte and insert separators.
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40); digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
constexpr size_t len = 8; constexpr const size_t len = 8;
if (const_check(is_big_endian())) { if (const_check(is_big_endian())) {
char tmp[len]; char tmp[len];
std::memcpy(tmp, &digits, len); std::memcpy(tmp, &digits, len);
@ -911,16 +1000,16 @@ template <typename T>
struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {}; struct has_tm_zone<T, void_t<decltype(T::tm_zone)>> : std::true_type {};
template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(has_tm_zone<T>::value)>
auto set_tm_zone(T& time, char* tz) -> bool { bool set_tm_zone(T& time, char* tz) {
time.tm_zone = tz; time.tm_zone = tz;
return true; return true;
} }
template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)> template <typename T, FMT_ENABLE_IF(!has_tm_zone<T>::value)>
auto set_tm_zone(T&, char*) -> bool { bool set_tm_zone(T&, char*) {
return false; return false;
} }
inline auto utc() -> char* { inline char* utc() {
static char tz[] = "UTC"; static char tz[] = "UTC";
return tz; return tz;
} }
@ -1594,13 +1683,8 @@ class get_locale {
public: public:
inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) { inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
if (!localized) return; if (localized)
ignore_unused(loc); ::new (&locale_) std::locale(loc.template get<std::locale>());
::new (&locale_) std::locale(
#if FMT_USE_LOCALE
loc.template get<std::locale>()
#endif
);
} }
inline ~get_locale() { inline ~get_locale() {
if (has_locale_) locale_.~locale(); if (has_locale_) locale_.~locale();
@ -2146,7 +2230,7 @@ template <typename Char> struct formatter<std::tm, Char> {
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
auto loc_ref = specs.localized() ? ctx.locale() : locale_ref(); auto loc_ref = specs.localized() ? ctx.locale() : detail::locale_ref();
detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref); detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>( auto w = detail::tm_writer<basic_appender<Char>, Char, Duration>(
loc, out, tm, subsecs); loc, out, tm, subsecs);

View File

@ -375,17 +375,19 @@ template <typename Char> struct ansi_color_escape {
// 10 more. // 10 more.
if (is_background) value += 10u; if (is_background) value += 10u;
buffer[size++] = static_cast<Char>('\x1b'); size_t index = 0;
buffer[size++] = static_cast<Char>('['); buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
if (value >= 100u) { if (value >= 100u) {
buffer[size++] = static_cast<Char>('1'); buffer[index++] = static_cast<Char>('1');
value %= 100u; value %= 100u;
} }
buffer[size++] = static_cast<Char>('0' + value / 10u); buffer[index++] = static_cast<Char>('0' + value / 10u);
buffer[size++] = static_cast<Char>('0' + value % 10u); buffer[index++] = static_cast<Char>('0' + value % 10u);
buffer[size++] = static_cast<Char>('m'); buffer[index++] = static_cast<Char>('m');
buffer[index++] = static_cast<Char>('\0');
return; return;
} }
@ -396,7 +398,7 @@ template <typename Char> struct ansi_color_escape {
to_esc(color.r, buffer + 7, ';'); to_esc(color.r, buffer + 7, ';');
to_esc(color.g, buffer + 11, ';'); to_esc(color.g, buffer + 11, ';');
to_esc(color.b, buffer + 15, 'm'); to_esc(color.b, buffer + 15, 'm');
size = 19; buffer[19] = static_cast<Char>(0);
} }
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept { FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
uint8_t em_codes[num_emphases] = {}; uint8_t em_codes[num_emphases] = {};
@ -409,28 +411,26 @@ template <typename Char> struct ansi_color_escape {
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8; if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9; if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
buffer[size++] = static_cast<Char>('\x1b'); size_t index = 0;
buffer[size++] = static_cast<Char>('[');
for (size_t i = 0; i < num_emphases; ++i) { for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue; if (!em_codes[i]) continue;
buffer[size++] = static_cast<Char>('0' + em_codes[i]); buffer[index++] = static_cast<Char>('\x1b');
buffer[size++] = static_cast<Char>(';'); buffer[index++] = static_cast<Char>('[');
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
buffer[index++] = static_cast<Char>('m');
} }
buffer[index++] = static_cast<Char>(0);
buffer[size - 1] = static_cast<Char>('m');
} }
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; } FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; } FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
FMT_CONSTEXPR auto end() const noexcept -> const Char* { FMT_CONSTEXPR20 auto end() const noexcept -> const Char* {
return buffer + size; return buffer + basic_string_view<Char>(buffer).size();
} }
private: private:
static constexpr size_t num_emphases = 8; static constexpr size_t num_emphases = 8;
Char buffer[7u + 4u * num_emphases] = {}; Char buffer[7u + 3u * num_emphases + 1u];
size_t size = 0;
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) noexcept { char delimiter) noexcept {

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -33,8 +33,8 @@
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
// Generate a unique explicit instantiation in every translation unit using a // Generate a unique explicit instantion in every translation unit using a tag
// tag type in an anonymous namespace. // type in an anonymous namespace.
namespace { namespace {
struct file_access_tag {}; struct file_access_tag {};
} // namespace } // namespace

View File

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

View File

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

View File

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

View File

@ -55,16 +55,6 @@ inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
#endif #endif
return false; return false;
} }
template <typename Char>
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args,
locale_ref loc = {}) {
static_assert(!std::is_same<Char, char>::value, "");
auto out = basic_appender<Char>(buf);
parse_format_string(
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
}
} // namespace detail } // namespace detail
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
@ -122,6 +112,10 @@ inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
return {{s}}; return {{s}};
} }
#ifdef __cpp_char8_t
template <> struct is_char<char8_t> : bool_constant<detail::is_utf8_enabled> {};
#endif
template <typename... T> template <typename... T>
constexpr auto make_wformat_args(T&... args) constexpr auto make_wformat_args(T&... args)
-> decltype(fmt::make_format_args<wformat_context>(args...)) { -> decltype(fmt::make_format_args<wformat_context>(args...)) {
@ -157,13 +151,13 @@ auto join(std::initializer_list<T> list, wstring_view sep)
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<Tuple, wchar_t> { -> tuple_join_view<wchar_t, Tuple> {
return {tuple, sep}; return {tuple, sep};
} }
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto vformat(basic_string_view<Char> fmt, auto vformat(basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, fmt, args); detail::vformat_to(buf, fmt, args);
@ -193,20 +187,24 @@ auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename S, typename Char = detail::format_string_char_t<S>, template <typename Locale, typename S,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)> typename Char = detail::format_string_char_t<S>,
inline auto vformat(locale_ref loc, const S& fmt, FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
basic_format_args<buffered_context<Char>> args) detail::is_exotic_char<Char>::value)>
inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
auto buf = basic_memory_buffer<Char>(); auto buf = basic_memory_buffer<Char>();
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc); detail::vformat_to(buf, detail::to_string_view(fmt), args,
detail::locale_ref(loc));
return {buf.data(), buf.size()}; return {buf.data(), buf.size()};
} }
template <typename S, typename... T, template <typename Locale, typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)> FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
inline auto format(locale_ref loc, const S& fmt, T&&... args) detail::is_exotic_char<Char>::value)>
inline auto format(const Locale& loc, const S& fmt, T&&... args)
-> std::basic_string<Char> { -> std::basic_string<Char> {
return vformat(loc, detail::to_string_view(fmt), return vformat(loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
@ -217,7 +215,7 @@ template <typename OutputIt, typename S,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
auto vformat_to(OutputIt out, const S& fmt, auto vformat_to(OutputIt out, const S& fmt,
basic_format_args<buffered_context<Char>> args) -> OutputIt { typename detail::vformat_args<Char>::type args) -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
detail::vformat_to(buf, detail::to_string_view(fmt), args); detail::vformat_to(buf, detail::to_string_view(fmt), args);
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
@ -233,24 +231,27 @@ inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
template <typename S, typename OutputIt, typename... Args, template <typename Locale, typename S, typename OutputIt, typename... Args,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_locale<Locale>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt, inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,
basic_format_args<buffered_context<Char>> args) typename detail::vformat_args<Char>::type args)
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
vformat_to(buf, detail::to_string_view(fmt), args, loc); vformat_to(buf, detail::to_string_view(fmt), args, detail::locale_ref(loc));
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
template <typename OutputIt, typename S, typename... T, template <typename Locale, typename OutputIt, typename S, typename... T,
typename Char = detail::format_string_char_t<S>, typename Char = detail::format_string_char_t<S>,
bool enable = detail::is_output_iterator<OutputIt, Char>::value && bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
detail::is_locale<Locale>::value &&
detail::is_exotic_char<Char>::value> detail::is_exotic_char<Char>::value>
inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args) inline auto format_to(OutputIt out, const Locale& loc, const S& fmt,
-> typename std::enable_if<enable, OutputIt>::type { T&&... args) ->
typename std::enable_if<enable, OutputIt>::type {
return vformat_to(out, loc, detail::to_string_view(fmt), return vformat_to(out, loc, detail::to_string_view(fmt),
fmt::make_format_args<buffered_context<Char>>(args...)); fmt::make_format_args<buffered_context<Char>>(args...));
} }
@ -259,7 +260,7 @@ template <typename OutputIt, typename Char, typename... Args,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&& FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
detail::is_exotic_char<Char>::value)> detail::is_exotic_char<Char>::value)>
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt, inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
basic_format_args<buffered_context<Char>> args) typename detail::vformat_args<Char>::type args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
@ -330,6 +331,18 @@ inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...)); return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
} }
template <typename... T>
FMT_DEPRECATED void print(std::FILE* f, text_style ts, wformat_string<T...> fmt,
const T&... args) {
vprint(f, ts, fmt, fmt::make_wformat_args(args...));
}
template <typename... T>
FMT_DEPRECATED void print(text_style ts, wformat_string<T...> fmt,
const T&... args) {
return print(stdout, ts, fmt, args...);
}
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) { inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
auto buffer = basic_memory_buffer<wchar_t>(); auto buffer = basic_memory_buffer<wchar_t>();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buffer, fmt, args);

View File

@ -50,8 +50,6 @@ module;
# include <limits.h> # include <limits.h>
# include <stdint.h> # include <stdint.h>
# include <stdio.h> # include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <time.h> # include <time.h>
#endif #endif
#include <cerrno> #include <cerrno>

View File

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

View File

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

View File

@ -0,0 +1 @@
8.1.1

22
support/bazel/BUILD.bazel Normal file
View File

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

View File

@ -0,0 +1,7 @@
module(
name = "fmt",
compatibility_level = 10,
)
bazel_dep(name = "platforms", version = "0.0.11")
bazel_dep(name = "rules_cc", version = "0.1.1")

28
support/bazel/README.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -55,8 +55,8 @@ auto make_second(int s) -> std::tm {
return time; return time;
} }
auto system_strftime(const std::string& format, const std::tm* timeptr, std::string system_strftime(const std::string& format, const std::tm* timeptr,
std::locale* locptr = nullptr) -> std::string { std::locale* locptr = nullptr) {
auto loc = locptr ? *locptr : std::locale::classic(); auto loc = locptr ? *locptr : std::locale::classic();
auto& facet = std::use_facet<std::time_put<char>>(loc); auto& facet = std::use_facet<std::time_put<char>>(loc);
std::ostringstream os; std::ostringstream os;
@ -73,8 +73,8 @@ auto system_strftime(const std::string& format, const std::tm* timeptr,
#endif #endif
} }
FMT_CONSTEXPR auto make_tm(int year, int mon, int mday, int hour, int min, FMT_CONSTEXPR std::tm make_tm(int year, int mon, int mday, int hour, int min,
int sec) -> std::tm { int sec) {
auto tm = std::tm(); auto tm = std::tm();
tm.tm_sec = sec; tm.tm_sec = sec;
tm.tm_min = min; tm.tm_min = min;
@ -336,12 +336,12 @@ TEST(chrono_test, local_time) {
} }
template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)> template <typename T, FMT_ENABLE_IF(fmt::detail::has_tm_gmtoff<T>::value)>
auto set_tm_gmtoff(T& time, long offset) -> bool { bool set_tm_gmtoff(T& time, long offset) {
time.tm_gmtoff = offset; time.tm_gmtoff = offset;
return true; return true;
} }
template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)> template <typename T, FMT_ENABLE_IF(!fmt::detail::has_tm_gmtoff<T>::value)>
auto set_tm_gmtoff(T&, long) -> bool { bool set_tm_gmtoff(T&, long) {
return false; return false;
} }

View File

@ -113,15 +113,6 @@ TEST(color_test, format) {
EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) | EXPECT_EQ(fmt::format("{}", fmt::styled("bar", fg(fmt::color::blue) |
fmt::emphasis::underline)), fmt::emphasis::underline)),
"\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m"); "\x1b[4m\x1b[38;2;000;000;255mbar\x1b[0m");
EXPECT_EQ(
fmt::format(
"{}", fmt::styled(
"all", fmt::emphasis::bold | fmt::emphasis::faint |
fmt::emphasis::italic |
fmt::emphasis::underline | fmt::emphasis::blink |
fmt::emphasis::reverse | fmt::emphasis::conceal |
fmt::emphasis::strikethrough)),
"\x1b[1;2;3;4;5;7;8;9mall\x1b[0m");
} }
TEST(color_test, format_to) { TEST(color_test, format_to) {

61
test/compile-fp-test.cc Normal file
View File

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

View File

@ -90,6 +90,9 @@ TEST(compile_test, format_escape) {
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc")); EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
} }
TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) { TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@ -121,6 +124,7 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo", "true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f, fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable())); "foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
} }
TEST(compile_test, named) { TEST(compile_test, named) {
@ -129,6 +133,10 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled), static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>); fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar", EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"), fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar"))); fmt::arg("a1", "bar")));
@ -228,12 +236,6 @@ TEST(compile_test, constexpr_formatted_size) {
fmt::formatted_size(FMT_COMPILE("{:s}"), "abc"); fmt::formatted_size(FMT_COMPILE("{:s}"), "abc");
EXPECT_EQ(str_size, 3); EXPECT_EQ(str_size, 3);
} }
TEST(compile_test, static_format) {
constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
EXPECT_STREQ(result.c_str(), "42");
EXPECT_EQ(result.str(), "42");
}
# endif # endif
TEST(compile_test, text_and_arg) { TEST(compile_test, text_and_arg) {
@ -310,6 +312,7 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals; using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf)); EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42)); EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
} }
#endif #endif
@ -417,53 +420,4 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) { TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42)); EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
} }
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif #endif
#if FMT_USE_CONSTEXPR_STRING
TEST(compile_test, constexpr_string_format) {
constexpr auto result = []() {
return fmt::format(FMT_COMPILE("{}"), 42) == "42";
}();
EXPECT_TRUE(result);
// Test with a larger string to avoid small string optimization.
constexpr auto big = []() {
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
}();
EXPECT_TRUE(big);
}
#endif // FMT_USE_CONSTEXPR_STRING

View File

@ -292,7 +292,7 @@ struct double_double {
auto format_as(double_double d) -> double { return d; } auto format_as(double_double d) -> double { return d; }
auto operator>=(const double_double& lhs, const double_double& rhs) -> bool { bool operator>=(const double_double& lhs, const double_double& rhs) {
return lhs.a + lhs.b >= rhs.a + rhs.b; return lhs.a + lhs.b >= rhs.a + rhs.b;
} }
@ -356,11 +356,11 @@ TEST(format_impl_test, write_console_signature) {
// A public domain branchless UTF-8 decoder by Christopher Wellons: // A public domain branchless UTF-8 decoder by Christopher Wellons:
// https://github.com/skeeto/branchless-utf8 // https://github.com/skeeto/branchless-utf8
constexpr auto unicode_is_surrogate(uint32_t c) -> bool { constexpr bool unicode_is_surrogate(uint32_t c) {
return c >= 0xD800U && c <= 0xDFFFU; return c >= 0xD800U && c <= 0xDFFFU;
} }
FMT_CONSTEXPR auto utf8_encode(char* s, uint32_t c) -> char* { FMT_CONSTEXPR char* utf8_encode(char* s, uint32_t c) {
if (c >= (1UL << 16)) { if (c >= (1UL << 16)) {
s[0] = static_cast<char>(0xf0 | (c >> 18)); s[0] = static_cast<char>(0xf0 | (c >> 18));
s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f)); s[1] = static_cast<char>(0x80 | ((c >> 12) & 0x3f));

View File

@ -206,6 +206,10 @@ TEST(util_test, parse_nonnegative_int) {
EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1); EXPECT_EQ(fmt::detail::parse_nonnegative_int(begin, end, -1), -1);
} }
TEST(format_impl_test, compute_width) {
EXPECT_EQ(fmt::detail::compute_width("вожык"), 5);
}
TEST(util_test, utf8_to_utf16) { TEST(util_test, utf8_to_utf16) {
auto u = fmt::detail::utf8_to_utf16("лошадка"); auto u = fmt::detail::utf8_to_utf16("лошадка");
EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str()); EXPECT_EQ(L"\x043B\x043E\x0448\x0430\x0434\x043A\x0430", u.str());
@ -315,17 +319,18 @@ TEST(memory_buffer_test, move_ctor_inline_buffer) {
std::allocator<char>* alloc = buffer.get_allocator().get(); std::allocator<char>* alloc = buffer.get_allocator().get();
basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer)); basic_memory_buffer<char, 5, std_allocator> buffer2(std::move(buffer));
// Move shouldn't destroy the inline content of the first buffer. // Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str); EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str); EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(buffer2.capacity(), 5u); EXPECT_EQ(5u, buffer2.capacity());
// Move should transfer allocator. // Move should transfer allocator.
EXPECT_EQ(buffer.get_allocator().get(), nullptr); EXPECT_EQ(nullptr, buffer.get_allocator().get());
EXPECT_EQ(buffer2.get_allocator().get(), alloc); EXPECT_EQ(alloc, buffer2.get_allocator().get());
}; };
auto alloc = std::allocator<char>(); auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc))); basic_memory_buffer<char, 5, std_allocator> buffer((std_allocator(&alloc)));
buffer.append(string_view("test")); const char test[] = "test";
buffer.append(string_view(test, 4));
check_move_buffer("test", buffer); check_move_buffer("test", buffer);
// Adding one more character fills the inline buffer, but doesn't cause // Adding one more character fills the inline buffer, but doesn't cause
// dynamic allocation. // dynamic allocation.
@ -350,63 +355,14 @@ TEST(memory_buffer_test, move_ctor_dynamic_buffer) {
EXPECT_GT(buffer2.capacity(), 4u); EXPECT_GT(buffer2.capacity(), 4u);
} }
using std_allocator_noprop = allocator_ref<std::allocator<char>, false>;
TEST(memory_buffer_test, move_ctor_inline_buffer_non_propagating) {
auto check_move_buffer =
[](const char* str,
basic_memory_buffer<char, 5, std_allocator_noprop>& buffer) {
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
const char* original_data_ptr = &buffer[0];
basic_memory_buffer<char, 5, std_allocator_noprop> buffer2(
std::move(buffer));
const char* new_data_ptr = &buffer2[0];
EXPECT_NE(new_data_ptr, original_data_ptr);
EXPECT_EQ(std::string(buffer.data(), buffer.size()), str);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), str);
EXPECT_EQ(buffer2.capacity(), 5u);
// Allocators should NOT be transferred; they remain distinct instances.
// The original buffer's allocator pointer should still be valid (not
// nullptr).
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
};
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 5, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
buffer.append(string_view("test", 4));
check_move_buffer("test", buffer);
buffer.push_back('a');
check_move_buffer("testa", buffer);
}
TEST(memory_buffer_test, move_ctor_dynamic_buffer_non_propagating) {
auto alloc = std::allocator<char>();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer(
(std_allocator_noprop(&alloc)));
const char test[] = "test";
buffer.append(test, test + 4);
const char* inline_buffer_ptr = &buffer[0];
buffer.push_back('a');
EXPECT_NE(buffer.data(), inline_buffer_ptr);
std::allocator<char>* original_alloc_ptr = buffer.get_allocator().get();
basic_memory_buffer<char, 4, std_allocator_noprop> buffer2;
buffer2 = std::move(buffer);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testa");
EXPECT_GT(buffer2.capacity(), 4u);
EXPECT_NE(buffer2.data(), inline_buffer_ptr);
EXPECT_EQ(buffer.get_allocator().get(), original_alloc_ptr);
EXPECT_NE(buffer2.get_allocator().get(), original_alloc_ptr);
}
void check_move_assign_buffer(const char* str, void check_move_assign_buffer(const char* str,
basic_memory_buffer<char, 5>& buffer) { basic_memory_buffer<char, 5>& buffer) {
basic_memory_buffer<char, 5> buffer2; basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer); buffer2 = std::move(buffer);
// Move shouldn't destroy the inline content of the first buffer. // Move shouldn't destroy the inline content of the first buffer.
EXPECT_EQ(std::string(&buffer[0], buffer.size()), str); EXPECT_EQ(str, std::string(&buffer[0], buffer.size()));
EXPECT_EQ(std::string(&buffer2[0], buffer2.size()), str); EXPECT_EQ(str, std::string(&buffer2[0], buffer2.size()));
EXPECT_EQ(buffer2.capacity(), 5u); EXPECT_EQ(5u, buffer2.capacity());
} }
TEST(memory_buffer_test, move_assignment) { TEST(memory_buffer_test, move_assignment) {
@ -425,8 +381,8 @@ TEST(memory_buffer_test, move_assignment) {
basic_memory_buffer<char, 5> buffer2; basic_memory_buffer<char, 5> buffer2;
buffer2 = std::move(buffer); buffer2 = std::move(buffer);
// Move should rip the guts of the first buffer. // Move should rip the guts of the first buffer.
EXPECT_EQ(buffer.data(), inline_buffer_ptr); EXPECT_EQ(inline_buffer_ptr, &buffer[0]);
EXPECT_EQ(std::string(buffer2.data(), buffer2.size()), "testab"); EXPECT_EQ("testab", std::string(&buffer2[0], buffer2.size()));
EXPECT_GT(buffer2.capacity(), 5u); EXPECT_GT(buffer2.capacity(), 5u);
} }
@ -525,12 +481,6 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
EXPECT_THROW(buffer.resize(161), std::exception); EXPECT_THROW(buffer.resize(161), std::exception);
} }
TEST(memory_buffer_test, back_insert_iterator) {
fmt::memory_buffer buf;
using iterator = decltype(std::back_inserter(buf));
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<iterator>::value);
}
TEST(format_test, digits2_alignment) { TEST(format_test, digits2_alignment) {
auto p = auto p =
fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0)); fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0));
@ -602,10 +552,6 @@ TEST(format_test, arg_errors) {
format_error, "argument not found"); format_error, "argument not found");
} }
TEST(format_test, display_width_precision) {
EXPECT_EQ(fmt::format("{:.5}", "🐱🐱🐱"), "🐱🐱");
}
template <int N> struct test_format { template <int N> struct test_format {
template <typename... T> template <typename... T>
static auto format(fmt::string_view fmt, const T&... args) -> std::string { static auto format(fmt::string_view fmt, const T&... args) -> std::string {
@ -931,7 +877,6 @@ TEST(format_test, width) {
" 0xcafe"); " 0xcafe");
EXPECT_EQ(fmt::format("{:11}", 'x'), "x "); EXPECT_EQ(fmt::format("{:11}", 'x'), "x ");
EXPECT_EQ(fmt::format("{:12}", "str"), "str "); EXPECT_EQ(fmt::format("{:12}", "str"), "str ");
EXPECT_EQ(fmt::format("{:*^5}", "🤡"), "*🤡**");
EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**"); EXPECT_EQ(fmt::format("{:*^6}", "🤡"), "**🤡**");
EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**"); EXPECT_EQ(fmt::format("{:*^8}", "你好"), "**你好**");
EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42."); EXPECT_EQ(fmt::format("{:#6}", 42.0), " 42.");
@ -939,31 +884,6 @@ TEST(format_test, width) {
EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0"); EXPECT_EQ(fmt::format("{:>06.0f}", 0.00884311), " 0");
} }
TEST(format_test, debug_presentation) {
EXPECT_EQ(fmt::format("{:?}", ""), R"("")");
EXPECT_EQ(fmt::format("{:*<5.0?}", "\n"), R"(*****)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "\n"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "\n"), R"("\***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "\n"), R"("\n**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", "\n"), R"("\n"*)");
EXPECT_EQ(fmt::format("{:*<5.1?}", "Σ"), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", "Σ"), R"("Σ***)");
EXPECT_EQ(fmt::format("{:*<5.3?}", "Σ"), R"("Σ"**)");
EXPECT_EQ(fmt::format("{:*<5.1?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.2?}", ""), R"("****)");
EXPECT_EQ(fmt::format("{:*<5.3?}", ""), R"("**)");
EXPECT_EQ(fmt::format("{:*<5.4?}", ""), R"(""*)");
EXPECT_EQ(fmt::format("{:*<8?}", "туда"), R"("туда"**)");
EXPECT_EQ(fmt::format("{:*>8?}", "сюда"), R"(**"сюда")");
EXPECT_EQ(fmt::format("{:*^8?}", "中心"), R"(*""*)");
EXPECT_EQ(fmt::format("{:*^14?}", "A\t👈🤯ы猫"), R"(*"A\t👈🤯ы"*)");
}
auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES auto bad_dynamic_spec_msg = FMT_BUILTIN_TYPES
? "width/precision is out of range" ? "width/precision is out of range"
: "width/precision is not integer"; : "width/precision is not integer";
@ -1165,6 +1085,9 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)), (void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
format_error, "invalid format specifier"); format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
EXPECT_THROW_MSG( EXPECT_THROW_MSG(
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304), (void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
format_error, "number is too big"); format_error, "number is too big");
@ -1176,34 +1099,9 @@ TEST(format_test, precision) {
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456"); EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
} }
TEST(format_test, large_precision) {
// Iterator used to abort the actual output.
struct throwing_iterator {
auto operator=(char) -> throwing_iterator& {
throw std::runtime_error("aborted");
return *this;
}
auto operator*() -> throwing_iterator& { return *this; }
auto operator++() -> throwing_iterator& { return *this; }
auto operator++(int) -> throwing_iterator { return *this; }
};
auto it = throwing_iterator();
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}}"), 1.0,
fmt::detail::max_value<int>()),
std::runtime_error, "aborted");
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}e}"), 1.0,
fmt::detail::max_value<int>() - 1),
std::runtime_error, "aborted");
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
}
TEST(format_test, utf8_precision) { TEST(format_test, utf8_precision) {
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
EXPECT_EQ(fmt::detail::compute_width(result), 4);
EXPECT_EQ(result, "caf\u00e9"); EXPECT_EQ(result, "caf\u00e9");
} }
@ -1857,15 +1755,19 @@ TEST(format_test, format_examples) {
fmt::format_to(std::back_inserter(out), "The answer is {}.", 42); fmt::format_to(std::back_inserter(out), "The answer is {}.", 42);
EXPECT_EQ("The answer is 42.", to_string(out)); EXPECT_EQ("The answer is 42.", to_string(out));
EXPECT_THROW( const char* filename = "nonexistent";
FILE* ftest = safe_fopen(filename, "r");
if (ftest) fclose(ftest);
int error_code = errno;
EXPECT_TRUE(ftest == nullptr);
EXPECT_SYSTEM_ERROR(
{ {
const char* filename = "madeup"; FILE* f = safe_fopen(filename, "r");
FILE* file = fopen(filename, "r"); if (!f)
if (!file) throw fmt::system_error(errno, "Cannot open file '{}'", filename);
throw fmt::system_error(errno, "cannot open file '{}'", filename); fclose(f);
fclose(file);
}, },
std::system_error); error_code, "Cannot open file 'nonexistent'");
EXPECT_EQ("First, thou shalt count to three", EXPECT_EQ("First, thou shalt count to three",
fmt::format("First, thou shalt count to {0}", "three")); fmt::format("First, thou shalt count to {0}", "three"));
@ -2036,6 +1938,11 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g')); 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
} }
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
static constexpr const char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) { TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo"); EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
@ -2050,12 +1957,19 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2"); EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
#endif #endif
static constexpr char format_str[3] = {'{', '}', '\0'}; (void)static_with_null;
(void)format_str; (void)static_no_null;
#ifndef _MSC_VER #ifndef _MSC_VER
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
#endif #endif
(void)with_null;
(void)no_null;
#if FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
#endif
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
#endif #endif
@ -2585,20 +2499,6 @@ TEST(format_test, writer) {
EXPECT_EQ(s.str(), "foo"); EXPECT_EQ(s.str(), "foo");
} }
#if FMT_USE_FCNTL && !defined(_WIN32)
TEST(format_test, invalid_glibc_buffer) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
auto file = write_end.get();
// This results in _IO_write_ptr < _IO_write_end.
fprintf(file, "111\n");
setvbuf(file, nullptr, _IOLBF, 0);
fmt::print(file, "------\n");
}
#endif // FMT_USE_FCNTL
#if FMT_USE_BITINT #if FMT_USE_BITINT
FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension") FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension")

View File

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

View File

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

View File

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

View File

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

View File

@ -19,21 +19,10 @@ using fmt::buffered_file;
using testing::HasSubstr; using testing::HasSubstr;
using wstring_view = fmt::basic_string_view<wchar_t>; using wstring_view = fmt::basic_string_view<wchar_t>;
static auto uniq_file_name(unsigned line_number) -> std::string { static std::string uniq_file_name(unsigned line_number) {
return "test-file" + std::to_string(line_number); return "test-file" + std::to_string(line_number);
} }
auto safe_fopen(const char* filename, const char* mode) -> FILE* {
#if defined(_WIN32) && !defined(__MINGW32__)
// Fix MSVC warning about "unsafe" fopen.
FILE* f = nullptr;
errno = fopen_s(&f, filename, mode);
return f;
#else
return std::fopen(filename, mode);
#endif
}
#ifdef _WIN32 #ifdef _WIN32
# include <windows.h> # include <windows.h>
@ -248,7 +237,8 @@ TEST(buffered_file_test, descriptor) {
} }
TEST(ostream_test, move) { TEST(ostream_test, move) {
fmt::ostream out = fmt::output_file(uniq_file_name(__LINE__)); auto test_file = uniq_file_name(__LINE__);
fmt::ostream out = fmt::output_file(test_file);
fmt::ostream moved(std::move(out)); fmt::ostream moved(std::move(out));
moved.print("hello"); moved.print("hello");
} }
@ -440,7 +430,8 @@ TEST(file_test, read) {
} }
TEST(file_test, read_error) { TEST(file_test, read_error) {
file f(uniq_file_name(__LINE__), file::WRONLY | file::CREATE); auto test_file = uniq_file_name(__LINE__);
file f(test_file, file::WRONLY | file::CREATE);
char buf; char buf;
// We intentionally read from a file opened in the write-only mode to // We intentionally read from a file opened in the write-only mode to
// cause error. // cause error.
@ -449,13 +440,15 @@ TEST(file_test, read_error) {
TEST(file_test, write) { TEST(file_test, write) {
auto pipe = fmt::pipe(); auto pipe = fmt::pipe();
write(pipe.write_end, "test"); auto test_file = uniq_file_name(__LINE__);
write(pipe.write_end, test_file);
pipe.write_end.close(); pipe.write_end.close();
EXPECT_READ(pipe.read_end, "test"); EXPECT_READ(pipe.read_end, test_file);
} }
TEST(file_test, write_error) { TEST(file_test, write_error) {
file f(uniq_file_name(__LINE__), file::RDONLY | file::CREATE); auto test_file = uniq_file_name(__LINE__);
file f(test_file, file::RDONLY | file::CREATE);
// We intentionally write to a file opened in the read-only mode to // We intentionally write to a file opened in the read-only mode to
// cause error. // cause error.
EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file"); EXPECT_SYSTEM_ERROR(f.write(" ", 1), EBADF, "cannot write to file");

View File

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

View File

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

View File

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

View File

@ -13,7 +13,6 @@
#include <vector> #include <vector>
#include "fmt/os.h" // fmt::system_category #include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith #include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem #ifdef __cpp_lib_filesystem
@ -39,12 +38,13 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")), L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына"); "Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80"); EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD <20> TAIL");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")),
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")), "HEAD \xF0\x9F\x98\x80 TAIL");
"[\xED\xA0\xBD\xF0\x9F\x98\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")),
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\""); "HEAD <20>\xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
# endif # endif
} }
@ -145,7 +145,6 @@ TEST(std_test, optional) {
EXPECT_FALSE((fmt::is_formattable<unformattable>::value)); EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value)); EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value)); EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif #endif
} }
@ -197,33 +196,7 @@ class my_class {
return fmt::to_string(elm.av); return fmt::to_string(elm.av);
} }
}; };
class my_class_int {
public:
int av;
private:
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
};
} // namespace my_nso } // namespace my_nso
TEST(std_test, expected_format_as) {
#ifdef __cpp_lib_expected
EXPECT_EQ(
fmt::format(
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
"expected(\"first\")");
EXPECT_EQ(
fmt::format("{}",
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
"expected(\"7\")");
EXPECT_EQ(fmt::format("{}",
std::expected<my_nso::my_class_int, int>{
my_nso::my_class_int{8}}),
"expected(8)");
#endif
}
TEST(std_test, optional_format_as) { TEST(std_test, optional_format_as) {
#ifdef __cpp_lib_optional #ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
@ -232,8 +205,6 @@ TEST(std_test, optional_format_as) {
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}), EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
"optional(\"7\")"); "optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif #endif
} }
@ -303,24 +274,6 @@ TEST(std_test, variant) {
#endif #endif
} }
TEST(std_test, variant_format_as) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
"variant(\"first\")");
EXPECT_EQ(fmt::format(
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
"variant(\"first\")");
EXPECT_EQ(
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
"variant(\"7\")");
EXPECT_EQ(
fmt::format("{}",
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
"variant(8)");
#endif
}
TEST(std_test, error_code) { TEST(std_test, error_code) {
auto& generic = std::generic_category(); auto& generic = std::generic_category();
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42"); EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
@ -335,10 +288,6 @@ TEST(std_test, error_code) {
EXPECT_EQ(fmt::format("{:s}", ec), ec.message()); EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)), EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
"\"generic:42\""); "\"generic:42\"");
EXPECT_EQ(fmt::format("{}",
std::map<std::error_code, int>{
{std::error_code(42, generic), 0}}),
"{\"generic:42\": 0}");
} }
template <typename Catch> void exception_test() { template <typename Catch> void exception_test() {
@ -464,31 +413,5 @@ TEST(std_test, format_shared_ptr) {
TEST(std_test, format_reference_wrapper) { TEST(std_test, format_reference_wrapper) {
int num = 35; int num = 35;
EXPECT_EQ(fmt::to_string(std::cref(num)), "35"); EXPECT_EQ("35", fmt::to_string(std::cref(num)));
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
}
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
struct type_with_format_as {};
int format_as(type_with_format_as) { return 20; }
TEST(std_test, format_reference_wrapper_with_format_as) {
type_with_format_as t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
}
struct type_with_format_as_string {};
std::string format_as(type_with_format_as_string) { return "foo"; }
TEST(std_test, format_reference_wrapper_with_format_as_string) {
type_with_format_as_string t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
} }

View File

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

View File

@ -74,7 +74,6 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
TEST(xchar_test, format) { TEST(xchar_test, format) {
EXPECT_EQ(fmt::format(L"{}", 42), L"42"); EXPECT_EQ(fmt::format(L"{}", 42), L"42");
EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2"); EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2");
EXPECT_EQ(fmt::format(L"{}", 1e100), L"1e+100");
EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc"); EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc");
EXPECT_EQ(fmt::format(L"{}", L'z'), L"z"); EXPECT_EQ(fmt::format(L"{}", L'z'), L"z");
EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error); EXPECT_THROW(fmt::format(fmt::runtime(L"{:*\x343E}"), 42), fmt::format_error);
@ -172,13 +171,6 @@ TEST(xchar_test, join) {
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)"); EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
} }
#ifdef __cpp_lib_byte
TEST(xchar_test, join_bytes) {
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
EXPECT_EQ(fmt::format(L"{}", fmt::join(v, L", ")), L"1, 2, 3");
}
#endif
enum streamable_enum {}; enum streamable_enum {};
std::wostream& operator<<(std::wostream& os, streamable_enum) { std::wostream& operator<<(std::wostream& os, streamable_enum) {
@ -379,7 +371,7 @@ TEST(locale_test, int_formatter) {
f.parse(parse_ctx); f.parse(parse_ctx);
auto buf = fmt::memory_buffer(); auto buf = fmt::memory_buffer();
fmt::basic_format_context<fmt::appender, char> format_ctx( fmt::basic_format_context<fmt::appender, char> format_ctx(
fmt::appender(buf), {}, fmt::locale_ref(loc)); fmt::appender(buf), {}, fmt::detail::locale_ref(loc));
f.format(12345, format_ctx); f.format(12345, format_ctx);
EXPECT_EQ(fmt::to_string(buf), "12,345"); EXPECT_EQ(fmt::to_string(buf), "12,345");
} }