Merge commit '5ff285a9ed91944b686cd1d77ff28bafa9975695' into dev

This commit is contained in:
Simone
2025-01-07 15:10:49 +00:00
41 changed files with 1068 additions and 774 deletions

View File

@@ -10,18 +10,20 @@ jobs:
steps: steps:
- name: Build fuzzers - name: Build fuzzers
id: build id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@92182553173581f871130c71c71b17f003d47b0a
with: with:
oss-fuzz-project-name: 'fmt' oss-fuzz-project-name: 'fmt'
dry-run: false dry-run: false
language: c++ language: c++
- name: Run fuzzers - name: Run fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a # master uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@92182553173581f871130c71c71b17f003d47b0a
with: with:
oss-fuzz-project-name: 'fmt' oss-fuzz-project-name: 'fmt'
fuzz-seconds: 300 fuzz-seconds: 300
dry-run: false dry-run: false
language: c++ language: c++
- name: Upload crash - name: Upload crash
uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0 uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
if: failure() && steps.build.outcome == 'success' if: failure() && steps.build.outcome == 'success'

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Add Ubuntu mirrors - name: Add Ubuntu mirrors
run: | run: |

View File

@@ -13,14 +13,16 @@ jobs:
format_code: format_code:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Install clang-format - name: Install clang-format
uses: aminya/setup-cpp@290824452986e378826155f3379d31bce8753d76 # v0.37.0 run: |
with: wget https://apt.llvm.org/llvm.sh
clangformat: 17.0.5 sudo bash ./llvm.sh 17
sudo apt install clang-format-17
- name: Run clang-format - name: Run clang-format
run: | run: |
find include src -name '*.h' -o -name '*.cc' | xargs clang-format -i -style=file -fallback-style=none find include src -name '*.h' -o -name '*.cc' | \
xargs clang-format-17 -i -style=file -fallback-style=none
git diff --exit-code git diff --exit-code

View File

@@ -62,7 +62,7 @@ jobs:
shared: -DBUILD_SHARED_LIBS=ON shared: -DBUILD_SHARED_LIBS=ON
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Set timezone - name: Set timezone
run: sudo timedatectl set-timezone 'Asia/Yekaterinburg' run: sudo timedatectl set-timezone 'Asia/Yekaterinburg'

View File

@@ -25,7 +25,7 @@ jobs:
runs-on: '${{ matrix.os }}' runs-on: '${{ matrix.os }}'
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Set timezone - name: Set timezone
run: sudo systemsetup -settimezone 'Asia/Yekaterinburg' run: sudo systemsetup -settimezone 'Asia/Yekaterinburg'

View File

@@ -29,7 +29,7 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
with: with:
persist-credentials: false persist-credentials: false
@@ -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@4dd16135b69a43b6c8efb853346f8437d92d3c93 # v3.26.6 uses: github/codeql-action/upload-sarif@662472033e021d55d94146f66f6058822b0b39fd # v3.27.0
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@@ -36,7 +36,7 @@ jobs:
standard: 20 standard: 20
steps: steps:
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Set timezone - name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time" run: tzutil /s "Ekaterinburg Standard Time"
@@ -78,12 +78,12 @@ jobs:
- name: Set timezone - name: Set timezone
run: tzutil /s "Ekaterinburg Standard Time" run: tzutil /s "Ekaterinburg Standard Time"
shell: cmd shell: cmd
- uses: msys2/setup-msys2@5df0ca6cbf14efcd08f8d5bd5e049a3cc8e07fd2 # v2.24.0 - uses: msys2/setup-msys2@c52d1fa9c7492275e60fe763540fb601f5f232a1 # v2.25.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@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - 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 }

View File

@@ -426,7 +426,9 @@ if (FMT_INSTALL)
endif() endif()
# Install the library and headers. # Install the library and headers.
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name} install(TARGETS ${INSTALL_TARGETS}
COMPONENT core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR} LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR} ARCHIVE DESTINATION ${FMT_LIB_DIR}
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt" PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
@@ -439,13 +441,15 @@ if (FMT_INSTALL)
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake) FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
# Install version, config and target files. # Install version, config and target files.
install( install(FILES ${project_config} ${version_config}
FILES ${project_config} ${version_config} DESTINATION ${FMT_CMAKE_DIR}
DESTINATION ${FMT_CMAKE_DIR}) COMPONENT core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR} install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::) NAMESPACE fmt::
COMPONENT core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT core)
endif () endif ()
function(add_doc_target) function(add_doc_target)
@@ -481,7 +485,8 @@ 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 OPTIONAL) DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT doc OPTIONAL)
endfunction() endfunction()
if (FMT_DOC) if (FMT_DOC)

View File

@@ -1,6 +1,237 @@
# 11.1.0 - TBD # 11.1.1 - 2024-12-27
- Improved debug codegen. - Fixed ABI compatibility with earlier 11.x versions
(https://github.com/fmtlib/fmt/issues/4278).
- Defined CMake components (`core` and `doc`) to allow docs to be installed
separately (https://github.com/fmtlib/fmt/pull/4276).
Thanks @carlsmedstad.
# 11.1.0 - 2024-12-25
- Improved C++20 module support
(https://github.com/fmtlib/fmt/issues/4081,
https://github.com/fmtlib/fmt/pull/4083,
https://github.com/fmtlib/fmt/pull/4084,
https://github.com/fmtlib/fmt/pull/4152,
https://github.com/fmtlib/fmt/issues/4153,
https://github.com/fmtlib/fmt/pull/4169,
https://github.com/fmtlib/fmt/issues/4190,
https://github.com/fmtlib/fmt/issues/4234,
https://github.com/fmtlib/fmt/pull/4239).
Thanks @kamrann and @Arghnews.
- Reduced debug (unoptimized) binary code size and the number of template
instantiations when passing formatting arguments. For example, unoptimized
binary code size for `fmt::print("{}", 42)` was reduced by ~40% on GCC and
~60% on clang (x86-64).
GCC:
- Before: 161 instructions of which 105 are in reusable functions
([godbolt](https://www.godbolt.org/z/s9bGoo4ze)).
- After: 116 instructions of which 60 are in reusable functions
([godbolt](https://www.godbolt.org/z/r7GGGxMs6)).
Clang:
- Before: 310 instructions of which 251 are in reusable functions
([godbolt](https://www.godbolt.org/z/Ts88b7M9o)).
- After: 194 instructions of which 135 are in reusable functions
([godbolt](https://www.godbolt.org/z/vcrjP8ceW)).
- Added an experimental `fmt::writer` API that can be used for writing to
different destinations such as files or strings
(https://github.com/fmtlib/fmt/issues/2354).
For example ([godbolt](https://www.godbolt.org/z/rWoKfbP7e)):
```c++
#include <fmt/os.h>
void write_text(fmt::writer w) {
w.print("The answer is {}.", 42);
}
int main() {
// Write to FILE.
write_text(stdout);
// Write to fmt::ostream.
auto f = fmt::output_file("myfile");
write_text(f);
// Write to std::string.
auto sb = fmt::string_buffer();
write_text(sb);
std::string s = sb.str();
}
```
- Added width and alignment support to the formatter of `std::error_code`.
- Made `std::expected<void, E>` formattable
(https://github.com/fmtlib/fmt/issues/4145,
https://github.com/fmtlib/fmt/pull/4148).
For example ([godbolt](https://www.godbolt.org/z/hrj5c6G86)):
```c++
fmt::print("{}", std::expected<void, int>());
```
prints
```
expected()
```
Thanks @phprus.
- Made `fmt::is_formattable<void>` SFINAE-friendly
(https://github.com/fmtlib/fmt/issues/4147).
- Added support for `_BitInt` formatting when using clang
(https://github.com/fmtlib/fmt/issues/4007,
https://github.com/fmtlib/fmt/pull/4072,
https://github.com/fmtlib/fmt/issues/4140,
https://github.com/fmtlib/fmt/issues/4173,
https://github.com/fmtlib/fmt/pull/4176).
For example ([godbolt](https://www.godbolt.org/z/KWjbWec5z)):
```c++
using int42 = _BitInt(42);
fmt::print("{}", int42(100));
```
Thanks @Arghnews.
- Added the `n` specifier for tuples and pairs
(https://github.com/fmtlib/fmt/pull/4107). Thanks @someonewithpc.
- Added support for tuple-like types to `fmt::join`
(https://github.com/fmtlib/fmt/issues/4226,
https://github.com/fmtlib/fmt/pull/4230). Thanks @phprus.
- Made more types formattable at compile time
(https://github.com/fmtlib/fmt/pull/4127). Thanks @AnthonyVH.
- Implemented a more efficient compile-time `fmt::formatted_size`
(https://github.com/fmtlib/fmt/issues/4102,
https://github.com/fmtlib/fmt/pull/4103). Thanks @phprus.
- Fixed compile-time formatting of some string types
(https://github.com/fmtlib/fmt/pull/4065). Thanks @torshepherd.
- Made compiled version of `fmt::format_to` work with
`std::back_insert_iterator<std::vector<char>>`
(https://github.com/fmtlib/fmt/issues/4206,
https://github.com/fmtlib/fmt/pull/4211). Thanks @phprus.
- Added a formatter for `std::reference_wrapper`
(https://github.com/fmtlib/fmt/pull/4163,
https://github.com/fmtlib/fmt/pull/4164). Thanks @yfeldblum and @phprus.
- Added experimental padding support (glibc `strftime` extension) to `%m`, `%j`
and `%Y` (https://github.com/fmtlib/fmt/pull/4161). Thanks @KKhanhH.
- Made microseconds formatted as `us` instead of `µs` if the Unicode support is
disabled (https://github.com/fmtlib/fmt/issues/4088).
- Fixed an unreleased regression in transcoding of surrogate pairs
(https://github.com/fmtlib/fmt/issues/4094,
https://github.com/fmtlib/fmt/pull/4095). Thanks @phprus.
- Made `fmt::appender` satisfy `std::output_iterator` concept
(https://github.com/fmtlib/fmt/issues/4092,
https://github.com/fmtlib/fmt/pull/4093). Thanks @phprus.
- Made `std::iterator_traits<fmt::appender>` standard-conforming
(https://github.com/fmtlib/fmt/pull/4185). Thanks @CaseyCarter.
- Made it easier to reuse `fmt::formatter<std::string_view>` for types with
an implicit conversion to `std::string_view`
(https://github.com/fmtlib/fmt/issues/4036,
https://github.com/fmtlib/fmt/pull/4055). Thanks @Arghnews.
- Made it possible to disable `<filesystem>` use via `FMT_CPP_LIB_FILESYSTEM`
for compatibility with some video game console SDKs, e.g. Nintendo Switch SDK
(https://github.com/fmtlib/fmt/issues/4257,
https://github.com/fmtlib/fmt/pull/4258,
https://github.com/fmtlib/fmt/pull/4259). Thanks @W4RH4WK and @phprus.
- Fixed compatibility with platforms that use 80-bit `long double`
(https://github.com/fmtlib/fmt/issues/4245,
https://github.com/fmtlib/fmt/pull/4246). Thanks @jsirpoma.
- Added support for UTF-32 code units greater than `0xFFFF` in fill
(https://github.com/fmtlib/fmt/issues/4201).
- Fixed handling of legacy encodings on Windows with GCC
(https://github.com/fmtlib/fmt/issues/4162).
- Made `fmt::to_string` take `fmt::basic_memory_buffer` by const reference
(https://github.com/fmtlib/fmt/issues/4261,
https://github.com/fmtlib/fmt/pull/4262). Thanks @sascha-devel.
- Added `fmt::dynamic_format_arg_store::size`
(https://github.com/fmtlib/fmt/pull/4270). Thanks @hannes-harnisch.
- Removed the ability to control locale usage via an undocumented
`FMT_STATIC_THOUSANDS_SEPARATOR` in favor of `FMT_USE_LOCALE`.
- Renamed `FMT_EXCEPTIONS` to `FMT_USE_EXCEPTIONS` for consistency with other
similar macros.
- Improved include directory ordering to reduce the chance of including
incorrect headers when using multiple versions of {fmt}
(https://github.com/fmtlib/fmt/pull/4116). Thanks @cdzhan.
- Made it possible to compile a subset of {fmt} without the C++ runtime.
- Improved documentation and README
(https://github.com/fmtlib/fmt/pull/4066,
https://github.com/fmtlib/fmt/issues/4117,
https://github.com/fmtlib/fmt/issues/4203,
https://github.com/fmtlib/fmt/pull/4235). Thanks @zyctree and @nikola-sh.
- Improved the documentation generator (https://github.com/fmtlib/fmt/pull/4110,
https://github.com/fmtlib/fmt/pull/4115). Thanks @rturrado.
- Improved CI (https://github.com/fmtlib/fmt/pull/4155,
https://github.com/fmtlib/fmt/pull/4151). Thanks @phprus.
- Fixed various warnings and compilation issues
(https://github.com/fmtlib/fmt/issues/2708,
https://github.com/fmtlib/fmt/issues/4091,
https://github.com/fmtlib/fmt/issues/4109,
https://github.com/fmtlib/fmt/issues/4113,
https://github.com/fmtlib/fmt/issues/4125,
https://github.com/fmtlib/fmt/issues/4129,
https://github.com/fmtlib/fmt/pull/4130,
https://github.com/fmtlib/fmt/pull/4131,
https://github.com/fmtlib/fmt/pull/4132,
https://github.com/fmtlib/fmt/issues/4133,
https://github.com/fmtlib/fmt/issues/4144,
https://github.com/fmtlib/fmt/issues/4150,
https://github.com/fmtlib/fmt/issues/4158,
https://github.com/fmtlib/fmt/pull/4159,
https://github.com/fmtlib/fmt/issues/4160,
https://github.com/fmtlib/fmt/pull/4170,
https://github.com/fmtlib/fmt/issues/4177,
https://github.com/fmtlib/fmt/pull/4187,
https://github.com/fmtlib/fmt/pull/4188,
https://github.com/fmtlib/fmt/pull/4194,
https://github.com/fmtlib/fmt/pull/4200,
https://github.com/fmtlib/fmt/issues/4205,
https://github.com/fmtlib/fmt/issues/4207,
https://github.com/fmtlib/fmt/pull/4208,
https://github.com/fmtlib/fmt/pull/4210,
https://github.com/fmtlib/fmt/issues/4220,
https://github.com/fmtlib/fmt/issues/4231,
https://github.com/fmtlib/fmt/issues/4232,
https://github.com/fmtlib/fmt/pull/4233,
https://github.com/fmtlib/fmt/pull/4236,
https://github.com/fmtlib/fmt/pull/4267,
https://github.com/fmtlib/fmt/pull/4271).
Thanks @torsten48, @Arghnews, @tinfoilboy, @aminya, @Ottani, @zeroomega,
@c4v4, @kongy, @vinayyadav3016, @sergio-nsk, @phprus and @YexuanXiao.
# 11.0.2 - 2024-07-20 # 11.0.2 - 2024-07-20
@@ -272,6 +503,9 @@
- Fixed handling of negative ids in `fmt::basic_format_args::get` - Fixed handling of negative ids in `fmt::basic_format_args::get`
(https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota. (https://github.com/fmtlib/fmt/pull/3945). Thanks @marlenecota.
- Fixed handling of a buffer boundary on flush
(https://github.com/fmtlib/fmt/issues/4229).
- Improved named argument validation - Improved named argument validation
(https://github.com/fmtlib/fmt/issues/3817). (https://github.com/fmtlib/fmt/issues/3817).

View File

@@ -291,6 +291,7 @@ converts to `std::print`.)
- [ccache](https://ccache.dev/): a compiler cache - [ccache](https://ccache.dev/): a compiler cache
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an - [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
analytical database management system analytical database management system
- [ContextVision](https://www.contextvision.com/): medical imaging software
- [Contour](https://github.com/contour-terminal/contour/): a modern - [Contour](https://github.com/contour-terminal/contour/): a modern
terminal emulator terminal emulator
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous - [CUAUV](https://cuauv.org/): Cornell University\'s autonomous

View File

@@ -269,18 +269,16 @@ that support C++20 `consteval`. On older compilers you can use the
Unused arguments are allowed as in Python's `str.format` and ordinary functions. Unused arguments are allowed as in Python's `str.format` and ordinary functions.
::: basic_format_string See [Type Erasure](#type-erasure) for an example of how to enable compile-time
checks in your own functions with `fmt::format_string` while avoiding template
bloat.
::: fstring
::: format_string ::: format_string
::: runtime(string_view) ::: runtime(string_view)
### Named Arguments
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Type Erasure ### Type Erasure
You can create your own formatting function with compile-time checks and You can create your own formatting function with compile-time checks and
@@ -317,6 +315,12 @@ parameterized version.
::: basic_format_arg ::: basic_format_arg
### Named Arguments
::: arg(const Char*, const T&)
Named arguments are not supported in compile-time checks at the moment.
### Compatibility ### Compatibility
::: basic_string_view ::: basic_string_view
@@ -375,18 +379,17 @@ allocator:
using custom_string = using custom_string =
std::basic_string<char, std::char_traits<char>, custom_allocator>; std::basic_string<char, std::char_traits<char>, custom_allocator>;
custom_string vformat(custom_allocator alloc, fmt::string_view format_str, auto vformat(custom_allocator alloc, fmt::string_view fmt,
fmt::format_args args) { fmt::format_args args) -> custom_string {
auto buf = custom_memory_buffer(alloc); auto buf = custom_memory_buffer(alloc);
fmt::vformat_to(std::back_inserter(buf), format_str, args); fmt::vformat_to(std::back_inserter(buf), fmt, args);
return custom_string(buf.data(), buf.size(), alloc); return custom_string(buf.data(), buf.size(), alloc);
} }
template <typename ...Args> template <typename ...Args>
inline custom_string format(custom_allocator alloc, auto format(custom_allocator alloc, fmt::string_view fmt,
fmt::string_view format_str, const Args& ... args) -> custom_string {
const Args& ... args) { return vformat(alloc, fmt, fmt::make_format_args(args...));
return vformat(alloc, format_str, fmt::make_format_args(args...));
} }
The allocator will be used for the output container only. Formatting The allocator will be used for the output container only. Formatting
@@ -410,11 +413,11 @@ locale:
that take `std::locale` as a parameter. The locale type is a template 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(const Locale&, format_string<T...>, T&&...) ::: format(detail::locale_ref, format_string<T...>, T&&...)
::: format_to(OutputIt, const Locale&, format_string<T...>, T&&...) ::: format_to(OutputIt, detail::locale_ref, format_string<T...>, T&&...)
::: formatted_size(const Locale&, format_string<T...>, T&&...) ::: formatted_size(detail::locale_ref, format_string<T...>, T&&...)
<a id="legacy-checks"></a> <a id="legacy-checks"></a>
### Legacy Compile-Time Checks ### Legacy Compile-Time Checks
@@ -498,10 +501,13 @@ chrono-format-specifications).
- [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag) - [`std::atomic_flag`](https://en.cppreference.com/w/cpp/atomic/atomic_flag)
- [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset) - [`std::bitset`](https://en.cppreference.com/w/cpp/utility/bitset)
- [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code) - [`std::error_code`](https://en.cppreference.com/w/cpp/error/error_code)
- [`std::exception`](https://en.cppreference.com/w/cpp/error/exception)
- [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path) - [`std::filesystem::path`](https://en.cppreference.com/w/cpp/filesystem/path)
- [`std::monostate`](https://en.cppreference.com/w/cpp/utility/variant/monostate) - [`std::monostate`](
https://en.cppreference.com/w/cpp/utility/variant/monostate)
- [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional) - [`std::optional`](https://en.cppreference.com/w/cpp/utility/optional)
- [`std::source_location`](https://en.cppreference.com/w/cpp/utility/source_location) - [`std::source_location`](
https://en.cppreference.com/w/cpp/utility/source_location)
- [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id) - [`std::thread::id`](https://en.cppreference.com/w/cpp/thread/thread/id)
- [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant) - [`std::variant`](https://en.cppreference.com/w/cpp/utility/variant/variant)
@@ -509,7 +515,7 @@ chrono-format-specifications).
::: ptr(const std::shared_ptr<T>&) ::: ptr(const std::shared_ptr<T>&)
### Formatting Variants ### Variants
A `std::variant` is only formattable if every variant alternative is A `std::variant` is only formattable if every variant alternative is
formattable, and requires the `__cpp_lib_variant` [library formattable, and requires the `__cpp_lib_variant` [library
@@ -525,15 +531,32 @@ feature](https://en.cppreference.com/w/cpp/feature_test).
fmt::print("{}", std::variant<std::monostate, char>()); fmt::print("{}", std::variant<std::monostate, char>());
// Output: variant(monostate) // Output: variant(monostate)
## Bit-Fields and Packed Structs
To format a bit-field or a field of a struct with `__attribute__((packed))`
applied to it, you need to convert it to the underlying or compatible type via
a cast or a unary `+` ([godbolt](https://www.godbolt.org/z/3qKKs6T5Y)):
```c++
struct smol {
int bit : 1;
};
auto s = smol();
fmt::print("{}", +s.bit);
```
This is a known limitation of "perfect" forwarding in C++.
<a id="compile-api"></a> <a id="compile-api"></a>
## Format String Compilation ## Format String Compilation
`fmt/compile.h` provides format string compilation enabled via the `fmt/compile.h` provides format string compilation and compile-time
`FMT_COMPILE` macro or the `_cf` user-defined literal defined in (`constexpr`) formatting enabled via the `FMT_COMPILE` macro or the `_cf`
namespace `fmt::literals`. Format strings marked with `FMT_COMPILE` user-defined literal defined in namespace `fmt::literals`. Format strings
or `_cf` are parsed, checked and converted into efficient formatting marked with `FMT_COMPILE` or `_cf` are parsed, checked and converted into
code at compile-time. This supports arguments of built-in and string efficient formatting code at compile-time. This supports arguments of built-in
types as well as user-defined types with `format` functions 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: specializations. For example:

View File

@@ -202,7 +202,7 @@ For a static build, use the following subproject definition:
For the header-only version, use: For the header-only version, use:
fmt = subproject('fmt') fmt = subproject('fmt', default_options: ['header-only=true'])
fmt_dep = fmt.get_variable('fmt_header_only_dep') fmt_dep = fmt.get_variable('fmt_header_only_dep')
### Android NDK ### Android NDK

View File

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

View File

@@ -706,12 +706,12 @@ The available padding modifiers (*padding_modifier*) are:
| Type | Meaning | | Type | Meaning |
|-------|-----------------------------------------| |-------|-----------------------------------------|
| `'-'` | Pad a numeric result with spaces. | | `'_'` | Pad a numeric result with spaces. |
| `'_'` | Do not pad a numeric result string. | | `'-'` | Do not pad a numeric result string. |
| `'0'` | Pad a numeric result string with zeros. | | `'0'` | Pad a numeric result string with zeros. |
These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`, These modifiers are only supported for the `'H'`, `'I'`, `'M'`, `'S'`, `'U'`,
`'V'`, `'W'`, `'m'`, `'j'`, `'Y'` presentation types. `'V'`, `'W'`, `'Y'`, `'d'`, `'j'` and `'m'` presentation types.
## Range Format Specifications ## Range Format Specifications

View File

@@ -210,6 +210,9 @@ template <typename Context> class dynamic_format_arg_store {
data_.reserve(new_cap); data_.reserve(new_cap);
named_info_.reserve(new_cap_named); named_info_.reserve(new_cap_named);
} }
/// Returns the number of elements in the store.
size_t size() const noexcept { return data_.size(); }
}; };
FMT_END_NAMESPACE 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 110002 #define FMT_VERSION 110101
// Detect compiler versions. // Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__) #if defined(__clang__) && !defined(__ibmxl__)
@@ -146,6 +146,8 @@
// Use the provided definition. // Use the provided definition.
#elif defined(__GNUC__) && !defined(__EXCEPTIONS) #elif defined(__GNUC__) && !defined(__EXCEPTIONS)
# define FMT_USE_EXCEPTIONS 0 # define FMT_USE_EXCEPTIONS 0
#elif defined(__clang__) && !defined(__cpp_exceptions)
# define FMT_USE_EXCEPTIONS 0
#elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS #elif FMT_MSC_VERSION && !_HAS_EXCEPTIONS
# define FMT_USE_EXCEPTIONS 0 # define FMT_USE_EXCEPTIONS 0
#else #else
@@ -159,6 +161,20 @@
# define FMT_CATCH(x) if (false) # define FMT_CATCH(x) if (false)
#endif #endif
#ifdef FMT_NO_UNIQUE_ADDRESS
// Use the provided definition.
#elif FMT_CPLUSPLUS < 202002L
// Not supported.
#elif FMT_HAS_CPP_ATTRIBUTE(no_unique_address)
# define FMT_NO_UNIQUE_ADDRESS [[no_unique_address]]
// VS2019 v16.10 and later except clang-cl (https://reviews.llvm.org/D110485).
#elif FMT_MSC_VERSION >= 1929 && !FMT_CLANG_VERSION
# define FMT_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
#endif
#ifndef FMT_NO_UNIQUE_ADDRESS
# define FMT_NO_UNIQUE_ADDRESS
#endif
#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) #if FMT_HAS_CPP17_ATTRIBUTE(fallthrough)
# define FMT_FALLTHROUGH [[fallthrough]] # define FMT_FALLTHROUGH [[fallthrough]]
#elif defined(__clang__) #elif defined(__clang__)
@@ -332,6 +348,13 @@ struct monostate {
# define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0 # define FMT_ENABLE_IF(...) fmt::enable_if_t<(__VA_ARGS__), int> = 0
#endif #endif
template <typename T> constexpr auto min_of(T a, T b) -> T {
return a < b ? a : b;
}
template <typename T> constexpr auto max_of(T a, T b) -> T {
return a > b ? a : b;
}
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/.
@@ -355,7 +378,9 @@ constexpr auto is_constant_evaluated(bool default_value = false) noexcept
} }
// Suppresses "conditional expression is constant" warnings. // Suppresses "conditional expression is constant" warnings.
template <typename T> constexpr auto const_check(T value) -> T { return value; } template <typename T> FMT_ALWAYS_INLINE constexpr auto const_check(T val) -> T {
return val;
}
FMT_NORETURN FMT_API void assert_fail(const char* file, int line, FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message); const char* message);
@@ -394,7 +419,7 @@ inline auto map(uint128_opt) -> monostate { return {}; }
#endif #endif
#ifndef FMT_USE_BITINT #ifndef FMT_USE_BITINT
# define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1400) # define FMT_USE_BITINT (FMT_CLANG_VERSION >= 1500)
#endif #endif
#if FMT_USE_BITINT #if FMT_USE_BITINT
@@ -562,8 +587,8 @@ template <typename Char> class basic_string_view {
// Lexicographically compare this string reference to other. // Lexicographically compare this string reference to other.
FMT_CONSTEXPR auto compare(basic_string_view other) const -> int { FMT_CONSTEXPR auto compare(basic_string_view other) const -> int {
size_t str_size = size_ < other.size_ ? size_ : other.size_; int result =
int result = detail::compare(data_, other.data_, str_size); detail::compare(data_, other.data_, min_of(size_, other.size_));
if (result != 0) return result; if (result != 0) return result;
return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); return size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1);
} }
@@ -714,7 +739,7 @@ class basic_specs {
max_fill_size = 4 max_fill_size = 4
}; };
unsigned long data_ = 1 << fill_size_shift; size_t data_ = 1 << fill_size_shift;
// Character (code unit) type is erased to prevent template bloat. // Character (code unit) type is erased to prevent template bloat.
char fill_data_[max_fill_size] = {' '}; char fill_data_[max_fill_size] = {' '};
@@ -793,7 +818,8 @@ class basic_specs {
template <typename Char> constexpr auto fill_unit() const -> Char { template <typename Char> constexpr auto fill_unit() const -> Char {
using uchar = unsigned char; using uchar = unsigned char;
return static_cast<Char>(static_cast<uchar>(fill_data_[0]) | return static_cast<Char>(static_cast<uchar>(fill_data_[0]) |
(static_cast<uchar>(fill_data_[1]) << 8)); (static_cast<uchar>(fill_data_[1]) << 8) |
(static_cast<uchar>(fill_data_[2]) << 16));
} }
FMT_CONSTEXPR void set_fill(char c) { FMT_CONSTEXPR void set_fill(char c) {
@@ -809,12 +835,19 @@ class basic_specs {
unsigned uchar = static_cast<detail::unsigned_char<Char>>(s[0]); unsigned uchar = static_cast<detail::unsigned_char<Char>>(s[0]);
fill_data_[0] = static_cast<char>(uchar); fill_data_[0] = static_cast<char>(uchar);
fill_data_[1] = static_cast<char>(uchar >> 8); fill_data_[1] = static_cast<char>(uchar >> 8);
fill_data_[2] = static_cast<char>(uchar >> 16);
return; return;
} }
FMT_ASSERT(size <= max_fill_size, "invalid fill"); FMT_ASSERT(size <= max_fill_size, "invalid fill");
for (size_t i = 0; i < size; ++i) for (size_t i = 0; i < size; ++i)
fill_data_[i & 3] = static_cast<char>(s[i]); fill_data_[i & 3] = static_cast<char>(s[i]);
} }
FMT_CONSTEXPR void set_fill(const basic_specs& specs) {
set_fill_size(specs.fill_size());
for (size_t i = 0; i < max_fill_size; ++i)
fill_data_[i] = specs.fill_data_[i];
}
}; };
// Format specifiers for built-in and string types. // Format specifiers for built-in and string types.
@@ -842,7 +875,7 @@ template <typename Char = char> class parse_context {
using char_type = Char; using char_type = Char;
using iterator = const Char*; using iterator = const Char*;
explicit constexpr parse_context(basic_string_view<Char> fmt, constexpr explicit parse_context(basic_string_view<Char> fmt,
int next_arg_id = 0) int next_arg_id = 0)
: fmt_(fmt), next_arg_id_(next_arg_id) {} : fmt_(fmt), next_arg_id_(next_arg_id) {}
@@ -1012,15 +1045,15 @@ template <typename Char, typename T> struct named_arg : view {
static_assert(!is_named_arg<T>::value, "nested named arguments"); static_assert(!is_named_arg<T>::value, "nested named arguments");
}; };
template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; } template <bool B = false> constexpr auto count() -> int { return B ? 1 : 0; }
template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t { 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... Args> constexpr auto count_named_args() -> size_t { template <typename... Args> constexpr auto count_named_args() -> int {
return count<is_named_arg<Args>::value...>(); return count<is_named_arg<Args>::value...>();
} }
template <typename... Args> constexpr auto count_static_named_args() -> size_t { template <typename... Args> constexpr auto count_static_named_args() -> int {
return count<is_static_named_arg<Args>::value...>(); return count<is_static_named_arg<Args>::value...>();
} }
@@ -1180,7 +1213,7 @@ class compile_parse_context : public parse_context<Char> {
using base = parse_context<Char>; using base = parse_context<Char>;
public: public:
explicit FMT_CONSTEXPR compile_parse_context(basic_string_view<Char> fmt, FMT_CONSTEXPR explicit compile_parse_context(basic_string_view<Char> fmt,
int num_args, const type* types, int num_args, const type* types,
int next_arg_id = 0) int next_arg_id = 0)
: base(fmt, next_arg_id), num_args_(num_args), types_(types) {} : base(fmt, next_arg_id), num_args_(num_args), types_(types) {}
@@ -1627,16 +1660,16 @@ 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_[NUM_ARGS > 0 ? NUM_ARGS : 1]; type types_[max_of(1, NUM_ARGS)];
named_arg_info<Char> named_args_[NUM_NAMED_ARGS > 0 ? NUM_NAMED_ARGS : 1]; 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_[NUM_ARGS > 0 ? NUM_ARGS : 1]; parse_func parse_funcs_[max_of(1, NUM_ARGS)];
public: public:
template <typename... T> template <typename... T>
explicit FMT_CONSTEXPR format_string_checker(basic_string_view<Char> fmt, FMT_CONSTEXPR explicit format_string_checker(basic_string_view<Char> fmt,
arg_pack<T...>) arg_pack<T...>)
: types_{mapped_type_constant<T, Char>::value...}, : types_{mapped_type_constant<T, Char>::value...},
named_args_{}, named_args_{},
@@ -1694,7 +1727,7 @@ template <typename T> class buffer {
protected: protected:
// Don't initialize ptr_ since it is not accessed to save a few cycles. // Don't initialize ptr_ since it is not accessed to save a few cycles.
FMT_MSC_WARNING(suppress : 26495) FMT_MSC_WARNING(suppress : 26495)
FMT_CONSTEXPR20 buffer(grow_fun grow, size_t sz) noexcept FMT_CONSTEXPR buffer(grow_fun grow, size_t sz) noexcept
: size_(sz), capacity_(sz), grow_(grow) {} : size_(sz), capacity_(sz), grow_(grow) {}
constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0, constexpr buffer(grow_fun grow, T* p = nullptr, size_t sz = 0,
@@ -1740,7 +1773,7 @@ template <typename T> class buffer {
// the new elements may not be initialized. // the new elements may not be initialized.
FMT_CONSTEXPR void try_resize(size_t count) { FMT_CONSTEXPR void try_resize(size_t count) {
try_reserve(count); try_reserve(count);
size_ = count <= capacity_ ? count : capacity_; size_ = min_of(count, capacity_);
} }
// Tries increasing the buffer capacity to `new_capacity`. It can increase the // Tries increasing the buffer capacity to `new_capacity`. It can increase the
@@ -1788,9 +1821,9 @@ template <typename T> class buffer {
}; };
struct buffer_traits { struct buffer_traits {
explicit buffer_traits(size_t) {} constexpr explicit buffer_traits(size_t) {}
auto count() const -> size_t { return 0; } constexpr auto count() const -> size_t { return 0; }
auto limit(size_t size) -> size_t { return size; } constexpr auto limit(size_t size) const -> size_t { return size; }
}; };
class fixed_buffer_traits { class fixed_buffer_traits {
@@ -1799,12 +1832,12 @@ class fixed_buffer_traits {
size_t limit_; size_t limit_;
public: public:
explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} constexpr explicit fixed_buffer_traits(size_t limit) : limit_(limit) {}
auto count() const -> size_t { return count_; } constexpr auto count() const -> size_t { return count_; }
auto limit(size_t size) -> size_t { FMT_CONSTEXPR auto limit(size_t size) -> size_t {
size_t n = limit_ > count_ ? limit_ - count_ : 0; size_t n = limit_ > count_ ? limit_ - count_ : 0;
count_ += size; count_ += size;
return size < n ? size : n; return min_of(size, n);
} }
}; };
@@ -1962,15 +1995,37 @@ template <typename T = char> class counting_buffer : public buffer<T> {
template <typename T> template <typename T>
struct is_back_insert_iterator<basic_appender<T>> : std::true_type {}; struct is_back_insert_iterator<basic_appender<T>> : std::true_type {};
template <typename OutputIt, typename InputIt, typename = void>
struct has_back_insert_iterator_container_append : std::false_type {};
template <typename OutputIt, typename InputIt>
struct has_back_insert_iterator_container_append<
OutputIt, InputIt,
void_t<decltype(get_container(std::declval<OutputIt>())
.append(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&&
has_back_insert_iterator_container_append<
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 {
get_container(out).append(begin, end); get_container(out).append(begin, end);
return out; return out;
} }
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
!has_back_insert_iterator_container_append<
OutputIt, InputIt>::value)>
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
-> OutputIt {
auto& c = get_container(out);
c.insert(c.end(), begin, end);
return out;
}
template <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)>
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt { FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
@@ -2146,7 +2201,8 @@ template <typename Context> class value {
template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
value(const T& named_arg) : value(named_arg.value) {} value(const T& named_arg) : value(named_arg.value) {}
template <typename T, FMT_ENABLE_IF(use_formatter<T>::value)> template <typename T,
FMT_ENABLE_IF(use_formatter<T>::value || !FMT_BUILTIN_TYPES)>
FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {} FMT_CONSTEXPR20 FMT_INLINE value(T& x) : value(x, custom_tag()) {}
FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size) FMT_ALWAYS_INLINE value(const named_arg_info<char_type>* args, size_t size)
@@ -2220,9 +2276,12 @@ struct locale_ref {
public: public:
constexpr locale_ref() : locale_(nullptr) {} constexpr locale_ref() : locale_(nullptr) {}
template <typename Locale> explicit locale_ref(const Locale& loc);
explicit operator bool() const noexcept { return locale_ != nullptr; } template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
#endif locale_ref(const Locale& loc);
inline explicit operator bool() const noexcept { return locale_ != nullptr; }
#endif // FMT_USE_LOCALE
template <typename Locale> auto get() const -> Locale; template <typename Locale> auto get() const -> Locale;
}; };
@@ -2243,16 +2302,15 @@ constexpr auto make_descriptor() -> unsigned long long {
: is_unpacked_bit | NUM_ARGS; : is_unpacked_bit | NUM_ARGS;
} }
template <typename Context, size_t NUM_ARGS> template <typename Context, int NUM_ARGS>
using arg_t = conditional_t<NUM_ARGS <= max_packed_args, value<Context>, using arg_t = conditional_t<NUM_ARGS <= max_packed_args, value<Context>,
basic_format_arg<Context>>; basic_format_arg<Context>>;
template <typename Context, size_t NUM_ARGS, size_t NUM_NAMED_ARGS, 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.
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning. arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS];
arg_t<Context, NUM_ARGS> args[1 + (NUM_ARGS != 0 ? NUM_ARGS : +1)];
named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS]; named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS];
template <typename... T> template <typename... T>
@@ -2280,13 +2338,13 @@ struct named_arg_store {
// An array of references to arguments. It can be implicitly converted to // An array of references to arguments. It can be implicitly converted to
// `basic_format_args` for passing into type-erased formatting functions // `basic_format_args` for passing into type-erased formatting functions
// such as `vformat`. It is a plain struct to reduce binary size in debug mode. // such as `vformat`. It is a plain struct to reduce binary size in debug mode.
template <typename Context, size_t NUM_ARGS, size_t NUM_NAMED_ARGS, template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
unsigned long long DESC> unsigned long long DESC>
struct format_arg_store { 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>[NUM_ARGS != 0 ? NUM_ARGS : +1], 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;
}; };
@@ -2372,11 +2430,6 @@ template <typename T> class basic_appender {
detail::buffer<T>* container; detail::buffer<T>* container;
public: public:
using iterator_category = int;
using value_type = T;
using pointer = T*;
using reference = T&;
using difference_type = decltype(pointer() - pointer());
using container_type = detail::buffer<T>; using container_type = detail::buffer<T>;
FMT_CONSTEXPR basic_appender(detail::buffer<T>& buf) : container(&buf) {} FMT_CONSTEXPR basic_appender(detail::buffer<T>& buf) : container(&buf) {}
@@ -2503,7 +2556,7 @@ template <typename Context> class basic_format_args {
return static_cast<detail::type>((desc_ >> shift) & mask); return static_cast<detail::type>((desc_ >> shift) & mask);
} }
template <size_t NUM_ARGS, size_t NUM_NAMED_ARGS, unsigned long long DESC> template <int NUM_ARGS, int NUM_NAMED_ARGS, unsigned long long DESC>
using store = using store =
detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>; detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>;
@@ -2513,14 +2566,14 @@ template <typename Context> class basic_format_args {
constexpr basic_format_args() : desc_(0), args_(nullptr) {} constexpr basic_format_args() : desc_(0), args_(nullptr) {}
/// Constructs a `basic_format_args` object from `format_arg_store`. /// Constructs a `basic_format_args` object from `format_arg_store`.
template <size_t NUM_ARGS, size_t NUM_NAMED_ARGS, unsigned long long DESC, template <int NUM_ARGS, int NUM_NAMED_ARGS, unsigned long long DESC,
FMT_ENABLE_IF(NUM_ARGS <= detail::max_packed_args)> FMT_ENABLE_IF(NUM_ARGS <= detail::max_packed_args)>
constexpr FMT_ALWAYS_INLINE basic_format_args( constexpr FMT_ALWAYS_INLINE basic_format_args(
const store<NUM_ARGS, NUM_NAMED_ARGS, DESC>& s) const store<NUM_ARGS, NUM_NAMED_ARGS, DESC>& s)
: desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)),
values_(s.args) {} values_(s.args) {}
template <size_t NUM_ARGS, size_t NUM_NAMED_ARGS, unsigned long long DESC, template <int NUM_ARGS, int NUM_NAMED_ARGS, unsigned long long DESC,
FMT_ENABLE_IF(NUM_ARGS > detail::max_packed_args)> FMT_ENABLE_IF(NUM_ARGS > detail::max_packed_args)>
constexpr basic_format_args(const store<NUM_ARGS, NUM_NAMED_ARGS, DESC>& s) constexpr basic_format_args(const store<NUM_ARGS, NUM_NAMED_ARGS, DESC>& s)
: desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)), : desc_(DESC | (NUM_NAMED_ARGS != 0 ? +detail::has_named_args_bit : 0)),
@@ -2571,10 +2624,11 @@ template <typename Context> class basic_format_args {
}; };
// A formatting context. // A formatting context.
class context : private detail::locale_ref { class context {
private: private:
appender out_; appender out_;
format_args args_; format_args args_;
FMT_NO_UNIQUE_ADDRESS detail::locale_ref loc_;
public: public:
/// The character type for the output. /// The character type for the output.
@@ -2590,24 +2644,26 @@ class context : private detail::locale_ref {
/// in the object so make sure they have appropriate lifetimes. /// in the object so make sure they have appropriate lifetimes.
FMT_CONSTEXPR context(iterator out, format_args args, FMT_CONSTEXPR context(iterator out, format_args args,
detail::locale_ref loc = {}) detail::locale_ref loc = {})
: locale_ref(loc), out_(out), args_(args) {} : out_(out), args_(args), loc_(loc) {}
context(context&&) = default; context(context&&) = default;
context(const context&) = delete; context(const context&) = delete;
void operator=(const context&) = delete; void operator=(const context&) = delete;
FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); } FMT_CONSTEXPR auto arg(int id) const -> format_arg { return args_.get(id); }
auto arg(string_view name) -> format_arg { return args_.get(name); } inline auto arg(string_view name) const -> format_arg {
FMT_CONSTEXPR auto arg_id(string_view name) -> int { return args_.get(name);
}
FMT_CONSTEXPR auto arg_id(string_view name) const -> int {
return args_.get_id(name); return args_.get_id(name);
} }
// Returns an iterator to the beginning of the output range. // Returns an iterator to the beginning of the output range.
FMT_CONSTEXPR auto out() -> iterator { return out_; } FMT_CONSTEXPR auto out() const -> iterator { return out_; }
// Advances the begin iterator to `it`. // Advances the begin iterator to `it`.
void advance_to(iterator) {} FMT_CONSTEXPR void advance_to(iterator) {}
FMT_CONSTEXPR auto locale() -> detail::locale_ref { return *this; } FMT_CONSTEXPR auto locale() const -> detail::locale_ref { return loc_; }
}; };
template <typename Char = char> struct runtime_format_string { template <typename Char = char> struct runtime_format_string {
@@ -2624,7 +2680,8 @@ template <typename Char = char> struct runtime_format_string {
*/ */
inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; } inline auto runtime(string_view s) -> runtime_format_string<> { return {{s}}; }
/// A compile-time format string. /// A compile-time format string. Use `format_string` in the public API to
/// prevent type deduction.
template <typename... T> struct fstring { template <typename... T> struct fstring {
private: private:
static constexpr int num_static_named_args = static constexpr int num_static_named_args =
@@ -2657,8 +2714,9 @@ template <typename... T> struct fstring {
template <typename S, template <typename S,
FMT_ENABLE_IF(std::is_convertible<const S&, string_view>::value)> FMT_ENABLE_IF(std::is_convertible<const S&, string_view>::value)>
FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) { FMT_CONSTEVAL FMT_ALWAYS_INLINE fstring(const S& s) : str(s) {
auto sv = string_view(str);
if (FMT_USE_CONSTEVAL) if (FMT_USE_CONSTEVAL)
detail::parse_format_string<char>(s, checker(s, arg_pack())); detail::parse_format_string<char>(sv, checker(sv, arg_pack()));
#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,
@@ -2711,8 +2769,8 @@ struct formatter<T, Char,
// Take arguments by lvalue references to avoid some lifetime issues, e.g. // Take arguments by lvalue references to avoid some lifetime issues, e.g.
// auto args = make_format_args(std::string()); // auto args = make_format_args(std::string());
template <typename Context = context, typename... T, template <typename Context = context, typename... T,
size_t NUM_ARGS = sizeof...(T), int NUM_ARGS = sizeof...(T),
size_t NUM_NAMED_ARGS = detail::count_named_args<T...>(), int NUM_NAMED_ARGS = detail::count_named_args<T...>(),
unsigned long long DESC = detail::make_descriptor<Context, T...>()> unsigned long long DESC = detail::make_descriptor<Context, T...>()>
constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args) constexpr FMT_ALWAYS_INLINE auto make_format_args(T&... args)
-> detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC> { -> detail::format_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC> {
@@ -2851,7 +2909,7 @@ FMT_API void vprint_buffered(FILE* f, string_view fmt, format_args args);
template <typename... T> template <typename... T>
FMT_INLINE void print(format_string<T...> fmt, T&&... args) { FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
vargs<T...> va = {{args...}}; vargs<T...> va = {{args...}};
if (!detail::use_utf8) if (detail::const_check(!detail::use_utf8))
return detail::vprint_mojibake(stdout, fmt.str, va, false); return detail::vprint_mojibake(stdout, fmt.str, va, false);
return detail::is_locking<T...>() ? vprint_buffered(stdout, fmt.str, va) return detail::is_locking<T...>() ? vprint_buffered(stdout, fmt.str, va)
: vprint(fmt.str, va); : vprint(fmt.str, va);
@@ -2868,7 +2926,8 @@ FMT_INLINE void print(format_string<T...> fmt, T&&... args) {
template <typename... T> template <typename... T>
FMT_INLINE void print(FILE* f, format_string<T...> fmt, T&&... args) { FMT_INLINE void print(FILE* f, format_string<T...> fmt, T&&... args) {
vargs<T...> va = {{args...}}; vargs<T...> va = {{args...}};
if (!detail::use_utf8) return detail::vprint_mojibake(f, fmt.str, va, false); if (detail::const_check(!detail::use_utf8))
return detail::vprint_mojibake(f, fmt.str, va, false);
return detail::is_locking<T...>() ? vprint_buffered(f, fmt.str, va) return detail::is_locking<T...>() ? vprint_buffered(f, fmt.str, va)
: vprint(f, fmt.str, va); : vprint(f, fmt.str, va);
} }
@@ -2878,8 +2937,9 @@ FMT_INLINE void print(FILE* f, format_string<T...> fmt, T&&... args) {
template <typename... T> template <typename... T>
FMT_INLINE void println(FILE* f, format_string<T...> fmt, T&&... args) { FMT_INLINE void println(FILE* f, format_string<T...> fmt, T&&... args) {
vargs<T...> va = {{args...}}; vargs<T...> va = {{args...}};
return detail::use_utf8 ? vprintln(f, fmt.str, va) return detail::const_check(detail::use_utf8)
: detail::vprint_mojibake(f, fmt.str, va, true); ? vprintln(f, fmt.str, va)
: detail::vprint_mojibake(f, fmt.str, va, true);
} }
/// Formats `args` according to specifications in `fmt` and writes the output /// Formats `args` according to specifications in `fmt` and writes the output

View File

@@ -444,7 +444,7 @@ struct is_same_arithmetic_type
std::is_floating_point<Rep2>::value)> { std::is_floating_point<Rep2>::value)> {
}; };
inline void throw_duration_error() { FMT_NORETURN inline void throw_duration_error() {
FMT_THROW(format_error("cannot format duration")); FMT_THROW(format_error("cannot format duration"));
} }
@@ -540,24 +540,24 @@ inline auto localtime(std::time_t time) -> std::tm {
std::time_t time_; std::time_t time_;
std::tm tm_; std::tm tm_;
dispatcher(std::time_t t) : time_(t) {} inline dispatcher(std::time_t t) : time_(t) {}
auto run() -> bool { inline auto run() -> bool {
using namespace fmt::detail; using namespace fmt::detail;
return handle(localtime_r(&time_, &tm_)); return handle(localtime_r(&time_, &tm_));
} }
auto handle(std::tm* tm) -> bool { return tm != nullptr; } inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }
auto handle(detail::null<>) -> bool { inline auto handle(detail::null<>) -> bool {
using namespace fmt::detail; using namespace fmt::detail;
return fallback(localtime_s(&tm_, &time_)); return fallback(localtime_s(&tm_, &time_));
} }
auto fallback(int res) -> bool { return res == 0; } inline auto fallback(int res) -> bool { return res == 0; }
#if !FMT_MSC_VERSION #if !FMT_MSC_VERSION
auto fallback(detail::null<>) -> bool { inline auto fallback(detail::null<>) -> bool {
using namespace fmt::detail; using namespace fmt::detail;
std::tm* tm = std::localtime(&time_); std::tm* tm = std::localtime(&time_);
if (tm) tm_ = *tm; if (tm) tm_ = *tm;
@@ -591,24 +591,24 @@ inline auto gmtime(std::time_t time) -> std::tm {
std::time_t time_; std::time_t time_;
std::tm tm_; std::tm tm_;
dispatcher(std::time_t t) : time_(t) {} inline dispatcher(std::time_t t) : time_(t) {}
auto run() -> bool { inline auto run() -> bool {
using namespace fmt::detail; using namespace fmt::detail;
return handle(gmtime_r(&time_, &tm_)); return handle(gmtime_r(&time_, &tm_));
} }
auto handle(std::tm* tm) -> bool { return tm != nullptr; } inline auto handle(std::tm* tm) -> bool { return tm != nullptr; }
auto handle(detail::null<>) -> bool { inline auto handle(detail::null<>) -> bool {
using namespace fmt::detail; using namespace fmt::detail;
return fallback(gmtime_s(&tm_, &time_)); return fallback(gmtime_s(&tm_, &time_));
} }
auto fallback(int res) -> bool { return res == 0; } inline auto fallback(int res) -> bool { return res == 0; }
#if !FMT_MSC_VERSION #if !FMT_MSC_VERSION
auto fallback(detail::null<>) -> bool { inline auto fallback(detail::null<>) -> bool {
std::tm* tm = std::gmtime(&time_); std::tm* tm = std::gmtime(&time_);
if (tm) tm_ = *tm; if (tm) tm_ = *tm;
return tm != nullptr; return tm != nullptr;
@@ -912,7 +912,9 @@ template <typename Derived> struct null_chrono_spec_handler {
}; };
struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> { struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); } FMT_NORETURN inline void unsupported() {
FMT_THROW(format_error("no format"));
}
template <typename Char> template <typename Char>
FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
@@ -1069,7 +1071,7 @@ void write_fractional_seconds(OutputIt& out, Duration d, int precision = -1) {
} }
} else if (precision > 0) { } else if (precision > 0) {
*out++ = '.'; *out++ = '.';
leading_zeroes = (std::min)(leading_zeroes, precision); leading_zeroes = min_of(leading_zeroes, precision);
int remaining = precision - leading_zeroes; int remaining = precision - leading_zeroes;
out = detail::fill_n(out, leading_zeroes, '0'); out = detail::fill_n(out, leading_zeroes, '0');
if (remaining < num_digits) { if (remaining < num_digits) {
@@ -1572,7 +1574,7 @@ class tm_writer {
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> { struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
bool has_precision_integral = false; bool has_precision_integral = false;
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); } FMT_NORETURN inline void unsupported() { FMT_THROW(format_error("no date")); }
template <typename Char> template <typename Char>
FMT_CONSTEXPR void on_text(const Char*, const Char*) {} FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
@@ -1693,14 +1695,14 @@ class get_locale {
bool has_locale_ = false; bool has_locale_ = false;
public: public:
get_locale(bool localized, locale_ref loc) : has_locale_(localized) { inline get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
if (localized) if (localized)
::new (&locale_) std::locale(loc.template get<std::locale>()); ::new (&locale_) std::locale(loc.template get<std::locale>());
} }
~get_locale() { inline ~get_locale() {
if (has_locale_) locale_.~locale(); if (has_locale_) locale_.~locale();
} }
operator const std::locale&() const { inline operator const std::locale&() const {
return has_locale_ ? locale_ : get_classic_locale(); return has_locale_ ? locale_ : get_classic_locale();
} }
}; };

View File

@@ -42,11 +42,10 @@ struct is_compiled_string : std::is_base_of<compiled_string, S> {};
#endif #endif
#if FMT_USE_NONTYPE_TEMPLATE_ARGS #if FMT_USE_NONTYPE_TEMPLATE_ARGS
template <typename Char, size_t N, template <typename Char, size_t N, fmt::detail::fixed_string<Char, N> Str>
fmt::detail_exported::fixed_string<Char, N> Str>
struct udl_compiled_string : compiled_string { struct udl_compiled_string : compiled_string {
using char_type = Char; using char_type = Char;
explicit constexpr operator basic_string_view<char_type>() const { constexpr explicit operator basic_string_view<char_type>() const {
return {Str.data, N - 1}; return {Str.data, N - 1};
} }
}; };
@@ -525,9 +524,9 @@ FMT_CONSTEXPR20 auto formatted_size(const S& fmt, const Args&... args)
template <typename S, typename... Args, template <typename S, typename... Args,
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)> FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
void print(std::FILE* f, const S& fmt, const Args&... args) { void print(std::FILE* f, const S& fmt, const Args&... args) {
memory_buffer buffer; auto buf = memory_buffer();
fmt::format_to(std::back_inserter(buffer), fmt, args...); fmt::format_to(appender(buf), fmt, args...);
detail::print(f, {buffer.data(), buffer.size()}); detail::print(f, {buf.data(), buf.size()});
} }
template <typename S, typename... Args, template <typename S, typename... Args,
@@ -538,7 +537,7 @@ void print(const S& fmt, const Args&... args) {
#if FMT_USE_NONTYPE_TEMPLATE_ARGS #if FMT_USE_NONTYPE_TEMPLATE_ARGS
inline namespace literals { inline namespace literals {
template <detail_exported::fixed_string Str> constexpr auto operator""_cf() { template <detail::fixed_string Str> constexpr auto operator""_cf() {
using char_t = remove_cvref_t<decltype(Str.data[0])>; using char_t = remove_cvref_t<decltype(Str.data[0])>;
return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t), return detail::udl_compiled_string<char_t, sizeof(Str.data) / sizeof(char_t),
Str>(); Str>();

View File

@@ -26,6 +26,10 @@
# include <locale> # include <locale>
#endif #endif
#ifndef FMT_FUNC
# define FMT_FUNC
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
@@ -59,8 +63,8 @@ FMT_FUNC void format_error_code(detail::buffer<char>& out, int error_code,
FMT_ASSERT(out.size() <= inline_buffer_size, ""); FMT_ASSERT(out.size() <= inline_buffer_size, "");
} }
FMT_FUNC void report_error(format_func func, int error_code, FMT_FUNC void do_report_error(format_func func, int error_code,
const char* message) noexcept { const char* message) noexcept {
memory_buffer full_message; memory_buffer full_message;
func(full_message, error_code, message); func(full_message, error_code, message);
// Don't use fwrite_all because the latter may throw. // Don't use fwrite_all because the latter may throw.
@@ -80,7 +84,7 @@ using std::locale;
using std::numpunct; using std::numpunct;
using std::use_facet; using std::use_facet;
template <typename Locale> template <typename Locale, enable_if_t<(sizeof(Locale::collate) != 0), int>>
locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { locale_ref::locale_ref(const Locale& loc) : locale_(&loc) {
static_assert(std::is_same<Locale, locale>::value, ""); static_assert(std::is_same<Locale, locale>::value, "");
} }
@@ -130,7 +134,9 @@ FMT_FUNC auto write_loc(appender out, loc_value value,
FMT_FUNC void report_error(const char* message) { FMT_FUNC void report_error(const char* message) {
#if FMT_USE_EXCEPTIONS #if FMT_USE_EXCEPTIONS
throw format_error(message); // Use FMT_THROW instead of throw to avoid bogus unreachable code warnings
// from MSVC.
FMT_THROW(format_error(message));
#else #else
fputs(message, stderr); fputs(message, stderr);
abort(); abort();
@@ -1430,7 +1436,7 @@ FMT_FUNC void format_system_error(detail::buffer<char>& out, int error_code,
FMT_FUNC void report_system_error(int error_code, FMT_FUNC void report_system_error(int error_code,
const char* message) noexcept { const char* message) noexcept {
report_error(format_system_error, error_code, message); do_report_error(format_system_error, error_code, message);
} }
FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string { FMT_FUNC auto vformat(string_view fmt, format_args args) -> std::string {

File diff suppressed because it is too large Load Diff

View File

@@ -176,24 +176,24 @@ class buffered_file {
friend class file; friend class file;
explicit buffered_file(FILE* f) : file_(f) {} inline explicit buffered_file(FILE* f) : file_(f) {}
public: public:
buffered_file(const buffered_file&) = delete; buffered_file(const buffered_file&) = delete;
void operator=(const buffered_file&) = delete; void operator=(const buffered_file&) = delete;
// Constructs a buffered_file object which doesn't represent any file. // Constructs a buffered_file object which doesn't represent any file.
buffered_file() noexcept : file_(nullptr) {} inline buffered_file() noexcept : file_(nullptr) {}
// Destroys the object closing the file it represents if any. // Destroys the object closing the file it represents if any.
FMT_API ~buffered_file() noexcept; FMT_API ~buffered_file() noexcept;
public: public:
buffered_file(buffered_file&& other) noexcept : file_(other.file_) { inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
other.file_ = nullptr; other.file_ = nullptr;
} }
auto operator=(buffered_file&& other) -> buffered_file& { inline auto operator=(buffered_file&& other) -> buffered_file& {
close(); close();
file_ = other.file_; file_ = other.file_;
other.file_ = nullptr; other.file_ = nullptr;
@@ -207,7 +207,7 @@ class buffered_file {
FMT_API void close(); FMT_API void close();
// Returns the pointer to a FILE object representing this file. // Returns the pointer to a FILE object representing this file.
auto get() const noexcept -> FILE* { return file_; } inline auto get() const noexcept -> FILE* { return file_; }
FMT_API auto descriptor() const -> int; FMT_API auto descriptor() const -> int;
@@ -248,7 +248,7 @@ class FMT_API file {
}; };
// Constructs a file object which doesn't represent any file. // Constructs a file object which doesn't represent any file.
file() noexcept : fd_(-1) {} inline file() noexcept : fd_(-1) {}
// Opens a file and constructs a file object representing this file. // Opens a file and constructs a file object representing this file.
file(cstring_view path, int oflag); file(cstring_view path, int oflag);
@@ -257,10 +257,10 @@ class FMT_API file {
file(const file&) = delete; file(const file&) = delete;
void operator=(const file&) = delete; void operator=(const file&) = delete;
file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; } inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
// Move assignment is not noexcept because close may throw. // Move assignment is not noexcept because close may throw.
auto operator=(file&& other) -> file& { inline auto operator=(file&& other) -> file& {
close(); close();
fd_ = other.fd_; fd_ = other.fd_;
other.fd_ = -1; other.fd_ = -1;
@@ -271,7 +271,7 @@ class FMT_API file {
~file() noexcept; ~file() noexcept;
// Returns the file descriptor. // Returns the file descriptor.
auto descriptor() const noexcept -> int { return fd_; } inline auto descriptor() const noexcept -> int { return fd_; }
// Closes the file. // Closes the file.
void close(); void close();
@@ -324,9 +324,9 @@ auto getpagesize() -> long;
namespace detail { namespace detail {
struct buffer_size { struct buffer_size {
buffer_size() = default; constexpr buffer_size() = default;
size_t value = 0; size_t value = 0;
auto operator=(size_t val) const -> buffer_size { FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
auto bs = buffer_size(); auto bs = buffer_size();
bs.value = val; bs.value = val;
return bs; return bs;
@@ -337,7 +337,7 @@ struct ostream_params {
int oflag = file::WRONLY | file::CREATE | file::TRUNC; int oflag = file::WRONLY | file::CREATE | file::TRUNC;
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768; size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
ostream_params() {} constexpr ostream_params() {}
template <typename... T> template <typename... T>
ostream_params(T... params, int new_oflag) : ostream_params(params...) { ostream_params(T... params, int new_oflag) : ostream_params(params...) {
@@ -381,7 +381,7 @@ class FMT_API ostream : private detail::buffer<char> {
return buf; return buf;
} }
void flush() { inline void flush() {
if (size() == 0) return; if (size() == 0) return;
file_.write(data(), size() * sizeof(data()[0])); file_.write(data(), size() * sizeof(data()[0]));
clear(); clear();
@@ -390,7 +390,7 @@ class FMT_API ostream : private detail::buffer<char> {
template <typename... T> template <typename... T>
friend auto output_file(cstring_view path, T... params) -> ostream; friend auto output_file(cstring_view path, T... params) -> ostream;
void close() { inline void close() {
flush(); flush();
file_.close(); file_.close();
} }

View File

@@ -22,6 +22,14 @@
#include "chrono.h" // formatbuf #include "chrono.h" // formatbuf
#ifdef _MSVC_STL_UPDATE
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
#else
# define FMT_MSVC_STL_UPDATE 0
#endif
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
namespace detail { namespace detail {
@@ -35,7 +43,7 @@ class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
}; };
#if FMT_MSC_VERSION #if FMT_MSVC_STL_UPDATE
template class file_access<file_access_tag, std::filebuf, template class file_access<file_access_tag, std::filebuf,
&std::filebuf::_Myfile>; &std::filebuf::_Myfile>;
auto get_file(std::filebuf&) -> FILE*; auto get_file(std::filebuf&) -> FILE*;
@@ -109,7 +117,7 @@ inline void vprint(std::ostream& os, string_view fmt, format_args args) {
auto buffer = memory_buffer(); auto buffer = memory_buffer();
detail::vformat_to(buffer, fmt, args); detail::vformat_to(buffer, fmt, args);
FILE* f = nullptr; FILE* f = nullptr;
#if FMT_MSC_VERSION && FMT_USE_RTTI #if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf())) if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
f = detail::get_file(*buf); f = detail::get_file(*buf);
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI #elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI

View File

@@ -79,7 +79,7 @@ template <bool IsSigned> struct int_checker {
unsigned max = to_unsigned(max_value<int>()); unsigned max = to_unsigned(max_value<int>());
return value <= max; return value <= max;
} }
static auto fits_in_int(bool) -> bool { return true; } inline static auto fits_in_int(bool) -> bool { return true; }
}; };
template <> struct int_checker<true> { template <> struct int_checker<true> {
@@ -87,7 +87,7 @@ template <> struct int_checker<true> {
return value >= (std::numeric_limits<int>::min)() && return value >= (std::numeric_limits<int>::min)() &&
value <= max_value<int>(); value <= max_value<int>();
} }
static auto fits_in_int(int) -> bool { return true; } inline static auto fits_in_int(int) -> bool { return true; }
}; };
struct printf_precision_handler { struct printf_precision_handler {
@@ -205,7 +205,7 @@ class printf_width_handler {
format_specs& specs_; format_specs& specs_;
public: public:
explicit printf_width_handler(format_specs& specs) : specs_(specs) {} inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)> template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
auto operator()(T value) -> unsigned { auto operator()(T value) -> unsigned {

View File

@@ -357,12 +357,9 @@ template <typename R>
using maybe_const_range = using maybe_const_range =
conditional_t<has_const_begin_end<R>::value, const R, R>; conditional_t<has_const_begin_end<R>::value, const R, R>;
// Workaround a bug in MSVC 2015 and earlier.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
template <typename R, typename Char> template <typename R, typename Char>
struct is_formattable_delayed struct is_formattable_delayed
: is_formattable<uncvref_type<maybe_const_range<R>>, Char> {}; : is_formattable<uncvref_type<maybe_const_range<R>>, Char> {};
#endif
} // namespace detail } // namespace detail
template <typename...> struct conjunction : std::true_type {}; template <typename...> struct conjunction : std::true_type {};
@@ -498,13 +495,8 @@ struct formatter<
range_format_kind<R, Char>::value != range_format::disabled && range_format_kind<R, Char>::value != range_format::disabled &&
range_format_kind<R, Char>::value != range_format::map && range_format_kind<R, Char>::value != range_format::map &&
range_format_kind<R, Char>::value != range_format::string && range_format_kind<R, Char>::value != range_format::string &&
range_format_kind<R, Char>::value != range_format::debug_string> range_format_kind<R, Char>::value != range_format::debug_string>,
// Workaround a bug in MSVC 2015 and earlier. detail::is_formattable_delayed<R, Char>>::value>> {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
,
detail::is_formattable_delayed<R, Char>
#endif
>::value>> {
private: private:
using range_type = detail::maybe_const_range<R>; using range_type = detail::maybe_const_range<R>;
range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_; range_formatter<detail::uncvref_type<range_type>, Char> range_formatter_;
@@ -646,9 +638,9 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
#endif #endif
formatter<remove_cvref_t<value_type>, Char> value_formatter_; formatter<remove_cvref_t<value_type>, Char> value_formatter_;
using view_ref = conditional_t<std::is_copy_constructible<It>::value, using view = conditional_t<std::is_copy_constructible<It>::value,
const join_view<It, Sentinel, Char>&, const join_view<It, Sentinel, Char>,
join_view<It, Sentinel, Char>&&>; join_view<It, Sentinel, Char>>;
public: public:
using nonlocking = void; using nonlocking = void;
@@ -658,9 +650,10 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
} }
template <typename FormatContext> template <typename FormatContext>
auto format(view_ref& value, FormatContext& ctx) const auto format(view& value, FormatContext& ctx) const -> decltype(ctx.out()) {
-> decltype(ctx.out()) { using iter =
auto it = std::forward<view_ref>(value).begin; conditional_t<std::is_copy_constructible<view>::value, It, It&>;
iter it = value.begin;
auto out = ctx.out(); auto out = ctx.out();
if (it == value.end) return out; if (it == value.end) return out;
out = value_formatter_.format(*it, ctx); out = value_formatter_.format(*it, ctx);
@@ -675,39 +668,11 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
} }
}; };
/// Returns a view that formats the iterator range `[begin, end)` with elements template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
/// separated by `sep`. const Tuple& tuple;
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
template <typename Char, typename... T> struct tuple_join_view : detail::view {
const std::tuple<T...>& tuple;
basic_string_view<Char> sep; basic_string_view<Char> sep;
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s) tuple_join_view(const Tuple& t, basic_string_view<Char> s)
: tuple(t), sep{s} {} : tuple(t), sep{s} {}
}; };
@@ -718,21 +683,22 @@ template <typename Char, typename... T> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0 # define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif #endif
template <typename Char, typename... T> template <typename Char, typename Tuple>
struct formatter<tuple_join_view<Char, T...>, Char> { struct formatter<tuple_join_view<Char, Tuple>, Char,
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::integral_constant<size_t, sizeof...(T)>()); return do_parse(ctx, std::tuple_size<Tuple>());
} }
template <typename FormatContext> template <typename FormatContext>
auto format(const tuple_join_view<Char, T...>& 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, return do_format(value, ctx, std::tuple_size<Tuple>());
std::integral_constant<size_t, sizeof...(T)>());
} }
private: private:
std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_; decltype(detail::tuple::get_formatters<Tuple, Char>(
detail::tuple_index_sequence<Tuple>())) formatters_;
FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx, FMT_CONSTEXPR auto do_parse(parse_context<Char>& ctx,
std::integral_constant<size_t, 0>) std::integral_constant<size_t, 0>)
@@ -746,7 +712,7 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
-> const Char* { -> const Char* {
auto end = ctx.begin(); auto end = ctx.begin();
#if FMT_TUPLE_JOIN_SPECIFIERS #if FMT_TUPLE_JOIN_SPECIFIERS
end = std::get<sizeof...(T) - N>(formatters_).parse(ctx); end = std::get<std::tuple_size<Tuple>::value - N>(formatters_).parse(ctx);
if (N > 1) { if (N > 1) {
auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>()); auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
if (end != end1) if (end != end1)
@@ -757,18 +723,20 @@ struct formatter<tuple_join_view<Char, T...>, Char> {
} }
template <typename FormatContext> template <typename FormatContext>
auto do_format(const tuple_join_view<Char, T...>&, 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<Char, T...>& 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 {
auto out = std::get<sizeof...(T) - N>(formatters_) using std::get;
.format(std::get<sizeof...(T) - N>(value.tuple), ctx); auto out =
std::get<std::tuple_size<Tuple>::value - N>(formatters_)
.format(get<std::tuple_size<Tuple>::value - N>(value.tuple), ctx);
if (N <= 1) return out; if (N <= 1) return out;
out = detail::copy<Char>(value.sep, out); out = detail::copy<Char>(value.sep, out);
ctx.advance_to(out); ctx.advance_to(out);
@@ -816,6 +784,34 @@ struct formatter<
FMT_BEGIN_EXPORT FMT_BEGIN_EXPORT
/// Returns a view that formats the iterator range `[begin, end)` with elements
/// separated by `sep`.
template <typename It, typename Sentinel>
auto join(It begin, Sentinel end, string_view sep) -> join_view<It, Sentinel> {
return {std::move(begin), end, sep};
}
/**
* Returns a view that formats `range` with elements separated by `sep`.
*
* **Example**:
*
* auto v = std::vector<int>{1, 2, 3};
* fmt::print("{}", fmt::join(v, ", "));
* // Output: 1, 2, 3
*
* `fmt::join` applies passed format specifiers to the range elements:
*
* fmt::print("{:02}", fmt::join(v, ", "));
* // Output: 01, 02, 03
*/
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& r, string_view sep)
-> join_view<decltype(detail::range_begin(r)),
decltype(detail::range_end(r))> {
return {detail::range_begin(r), detail::range_end(r), sep};
}
/** /**
* Returns an object that formats `std::tuple` with elements separated by `sep`. * Returns an object that formats `std::tuple` with elements separated by `sep`.
* *
@@ -825,9 +821,9 @@ FMT_BEGIN_EXPORT
* fmt::print("{}", fmt::join(t, ", ")); * fmt::print("{}", fmt::join(t, ", "));
* // Output: 1, a * // Output: 1, a
*/ */
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep) FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, T...> { -> tuple_join_view<char, Tuple> {
return {tuple, sep}; return {tuple, sep};
} }

View File

@@ -27,7 +27,8 @@
// 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
# if FMT_HAS_INCLUDE(<filesystem>) # if FMT_HAS_INCLUDE(<filesystem>) && \
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
# include <filesystem> # include <filesystem>
# endif # endif
# if FMT_HAS_INCLUDE(<variant>) # if FMT_HAS_INCLUDE(<variant>)
@@ -183,7 +184,8 @@ FMT_END_NAMESPACE
FMT_BEGIN_NAMESPACE FMT_BEGIN_NAMESPACE
FMT_EXPORT FMT_EXPORT
template <std::size_t N, typename Char> template <std::size_t N, typename Char>
struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> { struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
private: private:
// Functor because C++11 doesn't support generic lambdas. // Functor because C++11 doesn't support generic lambdas.
struct writer { struct writer {
@@ -203,7 +205,7 @@ struct formatter<std::bitset<N>, Char> : nested_formatter<string_view> {
template <typename FormatContext> template <typename FormatContext>
auto format(const std::bitset<N>& bs, FormatContext& ctx) const auto format(const std::bitset<N>& bs, FormatContext& ctx) const
-> decltype(ctx.out()) { -> decltype(ctx.out()) {
return write_padded(ctx, writer{bs}); return this->write_padded(ctx, writer{bs});
} }
}; };
@@ -433,8 +435,8 @@ template <> struct formatter<std::error_code> {
} }
template <typename FormatContext> template <typename FormatContext>
FMT_CONSTEXPR20 auto format(const std::error_code& ec, FormatContext& ctx) const FMT_CONSTEXPR20 auto format(const std::error_code& ec,
-> decltype(ctx.out()) { FormatContext& ctx) const -> decltype(ctx.out()) {
auto specs = specs_; auto specs = specs_;
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_, detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
ctx); ctx);
@@ -694,9 +696,7 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
auto outer_specs = format_specs(); auto outer_specs = format_specs();
outer_specs.width = specs.width; outer_specs.width = specs.width;
auto fill = specs.template fill<Char>(); outer_specs.set_fill(specs);
if (fill)
outer_specs.set_fill(basic_string_view<Char>(fill, specs.fill_size()));
outer_specs.set_align(specs.align()); outer_specs.set_align(specs.align());
specs.width = 0; specs.width = 0;

View File

@@ -140,7 +140,7 @@ auto join(It begin, Sentinel end, wstring_view sep)
return {begin, end, sep}; return {begin, end, sep};
} }
template <typename Range> template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
auto join(Range&& range, wstring_view sep) auto join(Range&& range, wstring_view sep)
-> join_view<decltype(std::begin(range)), decltype(std::end(range)), -> join_view<decltype(std::begin(range)), decltype(std::end(range)),
wchar_t> { wchar_t> {
@@ -153,9 +153,9 @@ auto join(std::initializer_list<T> list, wstring_view sep)
return join(std::begin(list), std::end(list), sep); return join(std::begin(list), std::end(list), sep);
} }
template <typename... T> template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const std::tuple<T...>& tuple, basic_string_view<wchar_t> sep) auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, T...> { -> tuple_join_view<wchar_t, Tuple> {
return {tuple, sep}; return {tuple, sep};
} }
@@ -191,11 +191,9 @@ 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 Locale, typename S, template <typename S, 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 vformat(detail::locale_ref loc, const S& fmt,
detail::is_exotic_char<Char>::value)>
inline auto vformat(const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type 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>();
@@ -204,11 +202,10 @@ inline auto vformat(const Locale& loc, const S& fmt,
return {buf.data(), buf.size()}; return {buf.data(), buf.size()};
} }
template <typename Locale, typename S, typename... T, template <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_locale<Locale>::value&& FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
detail::is_exotic_char<Char>::value)> inline auto format(detail::locale_ref loc, const S& fmt, T&&... args)
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...));
@@ -235,12 +232,11 @@ 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 Locale, typename S, typename OutputIt, typename... Args, template <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, detail::locale_ref loc, const S& fmt,
inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,
typename detail::vformat_args<Char>::type args) typename detail::vformat_args<Char>::type args)
-> OutputIt { -> OutputIt {
auto&& buf = detail::get_buffer<Char>(out); auto&& buf = detail::get_buffer<Char>(out);
@@ -248,12 +244,11 @@ inline auto vformat_to(OutputIt out, const Locale& loc, const S& fmt,
return detail::get_iterator(buf, out); return detail::get_iterator(buf, out);
} }
template <typename OutputIt, typename Locale, typename S, typename... T, template <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, const Locale& loc, const S& fmt, inline auto format_to(OutputIt out, detail::locale_ref loc, const S& fmt,
T&&... args) -> T&&... args) ->
typename std::enable_if<enable, OutputIt>::type { 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),

View File

@@ -16,6 +16,7 @@ template FMT_API auto dragonbox::to_decimal(double x) noexcept
-> dragonbox::decimal_fp<double>; -> dragonbox::decimal_fp<double>;
#if FMT_USE_LOCALE #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 locale_ref::locale_ref(const std::locale& loc);
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale; template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
#endif #endif

View File

@@ -160,7 +160,7 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
} }
void report_windows_error(int error_code, const char* message) noexcept { void report_windows_error(int error_code, const char* message) noexcept {
report_error(detail::format_windows_error, error_code, message); do_report_error(detail::format_windows_error, error_code, message);
} }
#endif // _WIN32 #endif // _WIN32

View File

@@ -1,10 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Manage site and releases. """Make a release.
Usage: Usage:
manage.py release [<branch>] release.py [<branch>]
manage.py site
For the release command $FMT_TOKEN should contain a GitHub personal access token For the release command $FMT_TOKEN should contain a GitHub personal access token
obtained from https://github.com/settings/tokens. obtained from https://github.com/settings/tokens.
@@ -12,9 +11,9 @@ obtained from https://github.com/settings/tokens.
from __future__ import print_function from __future__ import print_function
import datetime, docopt, errno, fileinput, json, os import datetime, docopt, errno, fileinput, json, os
import re, requests, shutil, sys import re, shutil, sys
from contextlib import contextmanager
from subprocess import check_call from subprocess import check_call
import urllib.request
class Git: class Git:
@@ -81,46 +80,15 @@ def create_build_env():
return env return env
fmt_repo_url = 'git@github.com:fmtlib/fmt' if __name__ == '__main__':
args = docopt.docopt(__doc__)
def update_site(env):
env.fmt_repo.update(fmt_repo_url)
doc_repo = Git(os.path.join(env.build_dir, 'fmt.dev'))
doc_repo.update('git@github.com:fmtlib/fmt.dev')
version = '11.0.0'
clean_checkout(env.fmt_repo, version)
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
# Build the docs.
html_dir = os.path.join(env.build_dir, 'html')
if os.path.exists(html_dir):
shutil.rmtree(html_dir)
include_dir = env.fmt_repo.dir
import build
build.build_docs(version, doc_dir=target_doc_dir,
include_dir=include_dir, work_dir=env.build_dir)
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
# Copy docs to the website.
version_doc_dir = os.path.join(doc_repo.dir, version)
try:
shutil.rmtree(version_doc_dir)
except OSError as e:
if e.errno != errno.ENOENT:
raise
shutil.move(html_dir, version_doc_dir)
def release(args):
env = create_build_env() env = create_build_env()
fmt_repo = env.fmt_repo fmt_repo = env.fmt_repo
branch = args.get('<branch>') branch = args.get('<branch>')
if branch is None: if branch is None:
branch = 'master' branch = 'master'
if not fmt_repo.update('-b', branch, fmt_repo_url): if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
clean_checkout(fmt_repo, branch) clean_checkout(fmt_repo, branch)
# Update the date in the changelog and extract the version and the first # Update the date in the changelog and extract the version and the first
@@ -191,28 +159,30 @@ def release(args):
# Create a release on GitHub. # Create a release on GitHub.
fmt_repo.push('origin', 'release') fmt_repo.push('origin', 'release')
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')} auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases', req = urllib.request.Request(
headers=auth_headers, 'https://api.github.com/repos/fmtlib/fmt/releases',
data=json.dumps({'tag_name': version, data=json.dumps({'tag_name': version,
'target_commitish': 'release', 'target_commitish': 'release',
'body': changes, 'draft': True})) 'body': changes, 'draft': True}).encode('utf-8'),
if r.status_code != 201: headers=auth_headers, method='POST')
raise Exception('Failed to create a release ' + str(r)) with urllib.request.urlopen(req) as response:
id = r.json()['id'] if response.status != 201:
raise Exception(f'Failed to create a release ' +
'{response.status} {response.reason}')
response_data = json.loads(response.read().decode('utf-8'))
id = response_data['id']
# Upload the package.
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases' uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
package = 'fmt-{}.zip'.format(version) package = 'fmt-{}.zip'.format(version)
r = requests.post( req = urllib.request.Request(
'{}/{}/assets?name={}'.format(uploads_url, id, package), f'{uploads_url}/{id}/assets?name={package}',
headers={'Content-Type': 'application/zip'} | auth_headers, headers={'Content-Type': 'application/zip'} | auth_headers,
data=open('build/fmt/' + package, 'rb')) data=open('build/fmt/' + package, 'rb').read(), method='POST')
if r.status_code != 201: with urllib.request.urlopen(req) as response:
raise Exception('Failed to upload an asset ' + str(r)) if response.status != 201:
raise Exception(f'Failed to upload an asset '
'{response.status} {response.reason}')
update_site(env) short_version = '.'.join(version.split('.')[:-1])
check_call(['./mkdocs', 'deploy', short_version])
if __name__ == '__main__':
args = docopt.docopt(__doc__)
if args.get('release'):
release(args)
elif args.get('site'):
update_site(create_build_env())

View File

@@ -62,13 +62,14 @@ 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 HEADER_ONLY) add_fmt_test(compile-fp-test)
if (MSVC) if (MSVC)
# Without this option, MSVC returns 199711L for the __cplusplus macro. # Without this option, MSVC returns 199711L for the __cplusplus macro.
target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus) target_compile_options(compile-fp-test PRIVATE /Zc:__cplusplus)
endif() 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(scan-test HEADER_ONLY) add_fmt_test(scan-test HEADER_ONLY)
check_symbol_exists(strptime "time.h" HAVE_STRPTIME) check_symbol_exists(strptime "time.h" HAVE_STRPTIME)

View File

@@ -186,3 +186,17 @@ TEST(args_test, move_constructor) {
store.reset(); store.reset();
EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo"); EXPECT_EQ(fmt::vformat("{} {} {a1}", moved_store), "42 foo foo");
} }
TEST(args_test, size) {
fmt::dynamic_format_arg_store<fmt::format_context> store;
EXPECT_EQ(store.size(), 0);
store.push_back(42);
EXPECT_EQ(store.size(), 1);
store.push_back("Molybdenum");
EXPECT_EQ(store.size(), 2);
store.clear();
EXPECT_EQ(store.size(), 0);
}

View File

@@ -645,9 +645,7 @@ TEST(base_test, is_formattable) {
EXPECT_TRUE(fmt::is_formattable<const const_formattable&>::value); EXPECT_TRUE(fmt::is_formattable<const const_formattable&>::value);
EXPECT_TRUE(fmt::is_formattable<nonconst_formattable&>::value); EXPECT_TRUE(fmt::is_formattable<nonconst_formattable&>::value);
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1910
EXPECT_FALSE(fmt::is_formattable<const nonconst_formattable&>::value); EXPECT_FALSE(fmt::is_formattable<const nonconst_formattable&>::value);
#endif
EXPECT_FALSE(fmt::is_formattable<convertible_to_pointer>::value); EXPECT_FALSE(fmt::is_formattable<convertible_to_pointer>::value);
const auto f = convertible_to_pointer_formattable(); const auto f = convertible_to_pointer_formattable();
@@ -745,19 +743,6 @@ TEST(base_test, no_implicit_conversion_to_string_view) {
fmt::is_formattable<implicitly_convertible_to_string_view>::value); fmt::is_formattable<implicitly_convertible_to_string_view>::value);
} }
#ifdef FMT_USE_STRING_VIEW
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(base_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
#endif
// std::is_constructible is broken in MSVC until version 2015.
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1900
struct explicitly_convertible_to_string_view { struct explicitly_convertible_to_string_view {
explicit operator fmt::string_view() const { return "foo"; } explicit operator fmt::string_view() const { return "foo"; }
}; };
@@ -769,7 +754,16 @@ TEST(base_test, format_explicitly_convertible_to_string_view) {
!fmt::is_formattable<explicitly_convertible_to_string_view>::value, ""); !fmt::is_formattable<explicitly_convertible_to_string_view>::value, "");
} }
# ifdef FMT_USE_STRING_VIEW #if FMT_CPLUSPLUS >= 201703L
struct implicitly_convertible_to_std_string_view {
operator std::string_view() const { return "foo"; }
};
TEST(base_test, no_implicit_conversion_to_std_string_view) {
EXPECT_FALSE(
fmt::is_formattable<implicitly_convertible_to_std_string_view>::value);
}
struct explicitly_convertible_to_std_string_view { struct explicitly_convertible_to_std_string_view {
explicit operator std::string_view() const { return "foo"; } explicit operator std::string_view() const { return "foo"; }
}; };
@@ -781,8 +775,7 @@ TEST(base_test, format_explicitly_convertible_to_std_string_view) {
!fmt::is_formattable<explicitly_convertible_to_std_string_view>::value, !fmt::is_formattable<explicitly_convertible_to_std_string_view>::value,
""); "");
} }
# endif #endif // FMT_CPLUSPLUS >= 201703L
#endif
TEST(base_test, has_formatter) { TEST(base_test, has_formatter) {
EXPECT_TRUE((fmt::detail::has_formatter<const const_formattable, char>())); EXPECT_TRUE((fmt::detail::has_formatter<const const_formattable, char>()));
@@ -868,3 +861,17 @@ TEST(base_test, format_to_custom_container) {
auto c = custom_container(); auto c = custom_container();
fmt::format_to(std::back_inserter(c), ""); fmt::format_to(std::back_inserter(c), "");
} }
struct nondeterministic_format_string {
mutable int i = 0;
FMT_CONSTEXPR operator string_view() const {
return string_view("{}", i++ != 0 ? 2 : 0);
}
};
TEST(base_test, no_repeated_format_string_conversions) {
#if !FMT_GCC_VERSION
char buf[10];
fmt::format_to(buf, nondeterministic_format_string());
#endif
}

View File

@@ -8,6 +8,7 @@
#include "fmt/compile.h" #include "fmt/compile.h"
#include <type_traits> #include <type_traits>
#include <vector>
#include "fmt/chrono.h" #include "fmt/chrono.h"
#include "fmt/ranges.h" #include "fmt/ranges.h"
@@ -229,10 +230,14 @@ TEST(compile_test, unknown_format_fallback) {
EXPECT_EQ(" 42 ", EXPECT_EQ(" 42 ",
fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42))); fmt::format(FMT_COMPILE("{name:^4}"), fmt::arg("name", 42)));
std::vector<char> v; std::vector<char> v1;
fmt::format_to(std::back_inserter(v), FMT_COMPILE("{name:^4}"), fmt::format_to(std::back_inserter(v1), FMT_COMPILE("{}"), 42);
EXPECT_EQ("42", fmt::string_view(v1.data(), v1.size()));
std::vector<char> v2;
fmt::format_to(std::back_inserter(v2), FMT_COMPILE("{name:^4}"),
fmt::arg("name", 42)); fmt::arg("name", 42));
EXPECT_EQ(" 42 ", fmt::string_view(v.data(), v.size())); EXPECT_EQ(" 42 ", fmt::string_view(v2.data(), v2.size()));
char buffer[4]; char buffer[4];
auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"), auto result = fmt::format_to_n(buffer, 4, FMT_COMPILE("{name:^5}"),
@@ -265,11 +270,23 @@ TEST(compile_test, to_string_and_formatter) {
fmt::format(FMT_COMPILE("{}"), to_stringable()); fmt::format(FMT_COMPILE("{}"), to_stringable());
} }
struct std_context_test {};
FMT_BEGIN_NAMESPACE
template <> struct formatter<std_context_test> : formatter<int> {
auto format(std_context_test, format_context& ctx) const
-> decltype(ctx.out()) {
return ctx.out();
}
};
FMT_END_NAMESPACE
TEST(compile_test, print) { TEST(compile_test, print) {
EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"), EXPECT_WRITE(stdout, fmt::print(FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!"); "Don't panic!");
EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"), EXPECT_WRITE(stderr, fmt::print(stderr, FMT_COMPILE("Don't {}!"), "panic"),
"Don't panic!"); "Don't panic!");
fmt::print(FMT_COMPILE("{}"), std_context_test());
} }
#endif #endif

View File

@@ -283,7 +283,7 @@ struct double_double {
double a; double a;
double b; double b;
explicit constexpr double_double(double a_val = 0, double b_val = 0) constexpr explicit double_double(double a_val = 0, double b_val = 0)
: a(a_val), b(b_val) {} : a(a_val), b(b_val) {}
operator double() const { return a + b; } operator double() const { return a + b; }
@@ -299,7 +299,7 @@ bool operator>=(const double_double& lhs, const double_double& rhs) {
struct slow_float { struct slow_float {
float value; float value;
explicit constexpr slow_float(float val = 0) : value(val) {} constexpr explicit slow_float(float val = 0) : value(val) {}
operator float() const { return value; } operator float() const { return value; }
auto operator-() const -> slow_float { return slow_float(-value); } auto operator-() const -> slow_float { return slow_float(-value); }
}; };

View File

@@ -1821,7 +1821,9 @@ TEST(format_test, big_print) {
} }
// Windows CRT implements _IOLBF incorrectly (full buffering). // Windows CRT implements _IOLBF incorrectly (full buffering).
#if FMT_USE_FCNTL && !defined(_WIN32) #if FMT_USE_FCNTL
# ifndef _WIN32
TEST(format_test, line_buffering) { TEST(format_test, line_buffering) {
auto pipe = fmt::pipe(); auto pipe = fmt::pipe();
@@ -1845,7 +1847,26 @@ TEST(format_test, line_buffering) {
reader.join(); reader.join();
} }
#endif # endif
TEST(format_test, buffer_boundary) {
auto pipe = fmt::pipe();
auto write_end = pipe.write_end.fdopen("w");
setvbuf(write_end.get(), nullptr, _IOFBF, 4096);
for (int i = 3; i < 4094; i++)
write_end.print("{}", (i % 73) != 0 ? 'x' : '\n');
write_end.print("{} {}", 1234, 567);
write_end.close();
auto read_end = pipe.read_end.fdopen("r");
char buf[4091] = {};
size_t n = fread(buf, 1, sizeof(buf), read_end.get());
EXPECT_EQ(n, sizeof(buf));
EXPECT_STREQ(fgets(buf, sizeof(buf), read_end.get()), "1234 567");
}
#endif // FMT_USE_FCNTL
struct deadlockable { struct deadlockable {
int value = 0; int value = 0;

View File

@@ -0,0 +1,24 @@
// Formatting library for C++ - formatting library tests
//
// Copyright (c) 2012 - present, Victor Zverovich
// All rights reserved.
//
// For the license information refer to format.h.
#include "gtest/gtest.h"
#if !defined(__GNUC__) || __GNUC__ >= 5
#define FMT_BUILTIN_TYPES 0
#include "fmt/format.h"
TEST(no_builtin_types_test, format) {
EXPECT_EQ(fmt::format("{}", 42), "42");
}
TEST(no_builtin_types_test, double_is_custom_type) {
double d = 42;
auto args = fmt::make_format_args(d);
EXPECT_EQ(fmt::format_args(args).get(0).type(),
fmt::detail::type::custom_type);
}
#endif

View File

@@ -28,11 +28,6 @@
# define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY # define FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
#endif #endif
#if !FMT_MSC_VERSION || FMT_MSC_VERSION > 1910
# define FMT_RANGES_TEST_ENABLE_JOIN
# define FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
#endif
#ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY #ifdef FMT_RANGES_TEST_ENABLE_C_STYLE_ARRAY
TEST(ranges_test, format_array) { TEST(ranges_test, format_array) {
int arr[] = {1, 2, 3, 5, 7, 11}; int arr[] = {1, 2, 3, 5, 7, 11};
@@ -213,7 +208,6 @@ TEST(ranges_test, tuple_parse_calls_element_parse) {
EXPECT_THROW(f.parse(ctx), bad_format); EXPECT_THROW(f.parse(ctx), bad_format);
} }
#ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
struct tuple_like { struct tuple_like {
int i; int i;
std::string str; std::string str;
@@ -246,7 +240,6 @@ TEST(ranges_test, format_struct) {
auto t = tuple_like{42, "foo"}; auto t = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")"); EXPECT_EQ(fmt::format("{}", t), "(42, \"foo\")");
} }
#endif // FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
TEST(ranges_test, format_to) { TEST(ranges_test, format_to) {
char buf[10]; char buf[10];
@@ -401,7 +394,6 @@ TEST(ranges_test, join_bytes) {
} }
#endif #endif
#ifdef FMT_RANGES_TEST_ENABLE_JOIN
TEST(ranges_test, join_tuple) { TEST(ranges_test, join_tuple) {
// Value tuple args. // Value tuple args.
auto t1 = std::tuple<char, int, float>('a', 1, 2.0f); auto t1 = std::tuple<char, int, float>('a', 1, 2.0f);
@@ -420,6 +412,10 @@ TEST(ranges_test, join_tuple) {
auto t4 = std::tuple<float>(4.0f); auto t4 = std::tuple<float>(4.0f);
EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4");
// Tuple-like.
auto t5 = tuple_like{42, "foo"};
EXPECT_EQ(fmt::format("{}", fmt::join(t5, ", ")), "42, foo");
# if FMT_TUPLE_JOIN_SPECIFIERS # if FMT_TUPLE_JOIN_SPECIFIERS
// Specs applied to each element. // Specs applied to each element.
auto t5 = std::tuple<int, int, long>(-3, 100, 1); auto t5 = std::tuple<int, int, long>(-3, 100, 1);
@@ -533,8 +529,6 @@ TEST(ranges_test, format_join_adl_begin_end) {
EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43"); EXPECT_EQ(fmt::format("{}", fmt::join(adl::vec(), "/")), "42/43");
} }
#endif // FMT_RANGES_TEST_ENABLE_JOIN
#if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 202207L
TEST(ranges_test, nested_ranges) { TEST(ranges_test, nested_ranges) {
auto l = std::list{1, 2, 3}; auto l = std::list{1, 2, 3};

View File

@@ -211,7 +211,7 @@ class scan_parse_context {
public: public:
using iterator = string_view::iterator; using iterator = string_view::iterator;
explicit FMT_CONSTEXPR scan_parse_context(string_view format) FMT_CONSTEXPR explicit scan_parse_context(string_view format)
: format_(format) {} : format_(format) {}
FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); } FMT_CONSTEXPR auto begin() const -> iterator { return format_.begin(); }
@@ -347,7 +347,7 @@ class scan_context {
using iterator = detail::scan_iterator; using iterator = detail::scan_iterator;
using sentinel = detail::scan_sentinel; using sentinel = detail::scan_sentinel;
explicit FMT_CONSTEXPR scan_context(detail::scan_buffer& buf, scan_args args) FMT_CONSTEXPR explicit scan_context(detail::scan_buffer& buf, scan_args args)
: buf_(buf), args_(args) {} : buf_(buf), args_(args) {}
FMT_CONSTEXPR auto arg(int id) const -> scan_arg { FMT_CONSTEXPR auto arg(int id) const -> scan_arg {

View File

@@ -91,6 +91,9 @@ TEST(std_test, complex) {
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)"); EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)"); EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "(1+2i) ");
EXPECT_EQ(fmt::format("{:-<8}", std::complex<double>(1, 2)), "(1+2i)--");
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)), EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
" (1.00+2.20i)"); " (1.00+2.20i)");
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)), EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),

View File

@@ -12,7 +12,7 @@
void throw_assertion_failure(const char* message); void throw_assertion_failure(const char* message);
#define FMT_ASSERT(condition, message) \ #define FMT_ASSERT(condition, message) \
if (!(condition)) throw_assertion_failure(message); ((condition) ? (void)0 : throw_assertion_failure(message))
#include "gtest/gtest.h" #include "gtest/gtest.h"

View File

@@ -72,16 +72,17 @@ TEST(xchar_test, format_explicitly_convertible_to_wstring_view) {
#endif #endif
TEST(xchar_test, format) { TEST(xchar_test, format) {
EXPECT_EQ(L"42", fmt::format(L"{}", 42)); EXPECT_EQ(fmt::format(L"{}", 42), L"42");
EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); EXPECT_EQ(fmt::format(L"{}", 4.2), L"4.2");
EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); EXPECT_EQ(fmt::format(L"{}", L"abc"), L"abc");
EXPECT_EQ(L"z", fmt::format(L"{}", 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);
EXPECT_EQ(L"true", fmt::format(L"{}", true)); EXPECT_EQ(fmt::format(L"{}", true), L"true");
EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); EXPECT_EQ(fmt::format(L"{0}", L'a'), L"a");
EXPECT_EQ(L"Cyrillic letter \x42e", EXPECT_EQ(fmt::format(L"Letter {}", L'\x40e'), L"Letter \x40e"); // Ў
fmt::format(L"Cyrillic letter {}", L'\x42e')); if (sizeof(wchar_t) == 4)
EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); EXPECT_EQ(fmt::format(fmt::runtime(L"{:𓀨>3}"), 42), L"𓀨42");
EXPECT_EQ(fmt::format(L"{}c{}", L"ab", 1), L"abc1");
} }
TEST(xchar_test, is_formattable) { TEST(xchar_test, is_formattable) {
@@ -490,12 +491,20 @@ TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50"); EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
} }
TEST(std_test_xchar, format_bitset) {
auto bs = std::bitset<6>(42);
EXPECT_EQ(fmt::format(L"{}", bs), L"101010");
EXPECT_EQ(fmt::format(L"{:0>8}", bs), L"00101010");
EXPECT_EQ(fmt::format(L"{:-^12}", bs), L"---101010---");
}
TEST(std_test_xchar, complex) { TEST(std_test_xchar, complex) {
auto s = fmt::format(L"{}", std::complex<double>(1, 2)); auto s = fmt::format(L"{}", std::complex<double>(1, 2));
EXPECT_EQ(s, L"(1+2i)"); EXPECT_EQ(s, L"(1+2i)");
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)), EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)),
L"(1.00+2.00i)"); L"(1.00+2.00i)");
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) "); EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1+2i) ");
EXPECT_EQ(fmt::format(L"{:-<8}", std::complex<double>(1, 2)), L"(1+2i)--");
} }
TEST(std_test_xchar, optional) { TEST(std_test_xchar, optional) {