Compare commits

..

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

28 changed files with 857 additions and 834 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ jobs:
steps: steps:
- name: "Checkout code" - name: "Checkout code"
uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
@ -52,7 +52,7 @@ jobs:
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab. # format to the repository Actions tab.
- name: "Upload artifact" - name: "Upload artifact"
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with: with:
name: SARIF file name: SARIF file
path: results.sarif path: results.sarif
@ -60,6 +60,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard. # Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning" - name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5 uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.29.5
with: with:
sarif_file: results.sarif sarif_file: results.sarif

View File

@ -32,7 +32,7 @@ jobs:
standard: 20 standard: 20
steps: steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set timezone - name: Set timezone
run: tzutil /s "FLE Standard Time" run: tzutil /s "FLE Standard Time"
@ -79,7 +79,7 @@ jobs:
release: false release: false
msystem: ${{matrix.sys}} msystem: ${{matrix.sys}}
pacboy: cc:p cmake:p ninja:p lld:p pacboy: cc:p cmake:p ninja:p lld:p
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.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

@ -12,7 +12,7 @@
alternative to C stdio and C++ iostreams. alternative to C stdio and C++ iostreams.
If you like this project, please consider donating to one of the funds If you like this project, please consider donating to one of the funds
that help victims of the war in Ukraine: <https://u24.gov.ua/>. that help victims of the war in Ukraine: <https://www.stopputin.net/>.
[Documentation](https://fmt.dev) [Documentation](https://fmt.dev)
@ -150,8 +150,8 @@ int main() {
} }
``` ```
This can be [up to 9 times faster than `fprintf`]( This can be [5 to 9 times faster than
http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html). fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
**Print with colors and text styles** **Print with colors and text styles**
@ -178,17 +178,17 @@ Output on a modern terminal with Unicode support:
| Library | Method | Run Time, s | | Library | Method | Run Time, s |
|-------------------|---------------|-------------| |-------------------|---------------|-------------|
| libc | printf | 0.66 | | libc | printf | 0.91 |
| libc++ | std::ostream | 1.63 | | libc++ | std::ostream | 2.49 |
| {fmt} 12.1 | fmt::print | 0.44 | | {fmt} 9.1 | fmt::print | 0.74 |
| Boost Format 1.88 | boost::format | 3.89 | | Boost Format 1.80 | boost::format | 6.26 |
| Folly Format | folly::format | 1.28 | | Folly Format | folly::format | 1.87 |
{fmt} is the fastest of the benchmarked methods, \~50% faster than {fmt} is the fastest of the benchmarked methods, \~20% faster than
`printf`. `printf`.
The above results were generated by building `tinyformat_test.cpp` on The above results were generated by building `tinyformat_test.cpp` on
macOS 15.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
taking the best of three runs. In the test, the format string taking the best of three runs. In the test, the format string
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000 `"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
times with output sent to `/dev/null`; for further details refer to the times with output sent to `/dev/null`; for further details refer to the
@ -216,26 +216,26 @@ in the following tables.
**Optimized build (-O3)** **Optimized build (-O3)**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | | Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------| |---------------|-----------------|----------------------|--------------------|
| printf | 1.6 | 54 | 50 | | printf | 1.6 | 54 | 50 |
| IOStreams | 28.4 | 98 | 84 | | IOStreams | 25.9 | 98 | 84 |
| {fmt} `1122268` | 5.0 | 54 | 50 | | fmt 83652df | 4.8 | 54 | 50 |
| tinyformat | 32.6 | 164 | 136 | | tinyformat | 29.1 | 161 | 136 |
| Boost Format | 55.0 | 530 | 317 | | Boost Format | 55.0 | 530 | 317 |
{fmt} is fast to compile and is comparable to `printf` in terms of per-call {fmt} is fast to compile and is comparable to `printf` in terms of per-call
binary size (within a rounding error on this system). binary size (within a rounding error on this system).
**Non-optimized build** **Non-optimized build**
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB | | Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|-----------------|-----------------|----------------------|--------------------| |---------------|-----------------|----------------------|--------------------|
| printf | 1.4 | 54 | 50 | | printf | 1.4 | 54 | 50 |
| IOStreams | 27.0 | 88 | 68 | | IOStreams | 23.4 | 92 | 68 |
| {fmt} `1122268` | 4.7 | 87 | 84 | | {fmt} 83652df | 4.4 | 89 | 85 |
| tinyformat | 28.1 | 185 | 145 | | tinyformat | 24.5 | 204 | 161 |
| Boost Format | 38.9 | 678 | 381 | | Boost Format | 36.4 | 831 | 462 |
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries `libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
to compare formatting function overhead only. Boost Format is a to compare formatting function overhead only. Boost Format is a

View File

@ -624,8 +624,6 @@ Example:
::: ostream ::: ostream
::: output_file(cstring_view, T...)
::: windows_error ::: windows_error
<a id="ostream-api"></a> <a id="ostream-api"></a>
@ -708,7 +706,7 @@ following differences:
precision that provides round-trip guarantees similarly to other languages precision that provides round-trip guarantees similarly to other languages
like Java and Python. `std::format` is currently specified in terms of like Java and Python. `std::format` is currently specified in terms of
`std::to_chars` which tries to generate the smallest number of characters `std::to_chars` which tries to generate the smallest number of characters
(ignoring redundant digits and sign in exponent) and may produce more (ignoring redundant digits and sign in exponent) and may procude more
decimal digits than necessary. decimal digits than necessary.
## Configuration Options ## Configuration Options
@ -748,7 +746,7 @@ configuring CMake.
- `0` - off (default) - `0` - off (default)
- `1` - disables locale support and applies some optimizations - `1` - disables locale support and applies some optimizations
- `2` - disables some Unicode features, named arguments and applies more - `2` - disables some Unicode features, named arguments and applies more
aggressive optimizations aggresive optimizations
### Binary Size Optimization ### Binary Size Optimization

View File

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

View File

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

View File

@ -233,6 +233,7 @@
FMT_PRAGMA_GCC(push_options) FMT_PRAGMA_GCC(push_options)
#if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE) #if !defined(__OPTIMIZE__) && !defined(__CUDACC__) && !defined(FMT_MODULE)
FMT_PRAGMA_GCC(optimize("Og")) FMT_PRAGMA_GCC(optimize("Og"))
# define FMT_GCC_OPTIMIZED
#endif #endif
FMT_PRAGMA_CLANG(diagnostic push) FMT_PRAGMA_CLANG(diagnostic push)
FMT_PRAGMA_GCC(diagnostic push) FMT_PRAGMA_GCC(diagnostic push)
@ -245,7 +246,7 @@ FMT_PRAGMA_GCC(diagnostic push)
# define FMT_ALWAYS_INLINE inline # define FMT_ALWAYS_INLINE inline
#endif #endif
// A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode. // A version of FMT_ALWAYS_INLINE to prevent code bloat in debug mode.
#ifdef NDEBUG #if defined(NDEBUG) || defined(FMT_GCC_OPTIMIZED)
# define FMT_INLINE FMT_ALWAYS_INLINE # define FMT_INLINE FMT_ALWAYS_INLINE
#else #else
# define FMT_INLINE inline # define FMT_INLINE inline
@ -926,7 +927,7 @@ class locale_ref {
template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)> template <typename Locale, FMT_ENABLE_IF(sizeof(Locale::collate) != 0)>
locale_ref(const Locale& loc) : locale_(&loc) { locale_ref(const Locale& loc) : locale_(&loc) {
// Check if std::isalpha is found via ADL to reduce the chance of misuse. // Check if std::isalpha is found via ADL to reduce the chance of misuse.
detail::ignore_unused(sizeof(isalpha('x', loc))); isalpha('x', loc);
} }
inline explicit operator bool() const noexcept { return locale_ != nullptr; } inline explicit operator bool() const noexcept { return locale_ != nullptr; }
@ -1850,7 +1851,8 @@ template <typename T> class buffer {
#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940 #if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1940
FMT_CONSTEXPR20 FMT_CONSTEXPR20
#endif #endif
void append(const U* begin, const U* end) { void
append(const U* begin, const U* end) {
while (begin != end) { while (begin != end) {
auto size = size_; auto size = size_;
auto free_cap = capacity_ - size; auto free_cap = capacity_ - size;
@ -2754,9 +2756,7 @@ template <typename... T> struct fstring {
static_assert(count<(is_view<remove_cvref_t<T>>::value && static_assert(count<(is_view<remove_cvref_t<T>>::value &&
std::is_reference<T>::value)...>() == 0, std::is_reference<T>::value)...>() == 0,
"passing views as lvalues is disallowed"); "passing views as lvalues is disallowed");
#if FMT_USE_CONSTEVAL if (FMT_USE_CONSTEVAL) parse_format_string<char>(s, checker(s, arg_pack()));
parse_format_string<char>(s, checker(s, arg_pack()));
#endif
#ifdef FMT_ENFORCE_COMPILE_STRING #ifdef FMT_ENFORCE_COMPILE_STRING
static_assert( static_assert(
FMT_USE_CONSTEVAL && sizeof(s) != 0, FMT_USE_CONSTEVAL && sizeof(s) != 0,

View File

@ -155,7 +155,7 @@ enum class color : uint32_t {
white_smoke = 0xF5F5F5, // rgb(245,245,245) white_smoke = 0xF5F5F5, // rgb(245,245,245)
yellow = 0xFFFF00, // rgb(255,255,0) yellow = 0xFFFF00, // rgb(255,255,0)
yellow_green = 0x9ACD32 // rgb(154,205,50) yellow_green = 0x9ACD32 // rgb(154,205,50)
}; // enum class color }; // enum class color
enum class terminal_color : uint8_t { enum class terminal_color : uint8_t {
black = 30, black = 30,

View File

@ -522,7 +522,7 @@ auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
-> format_to_n_result<OutputIt> { -> format_to_n_result<OutputIt> {
using traits = detail::fixed_buffer_traits; using traits = detail::fixed_buffer_traits;
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n); auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...); fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
return {buf.out(), buf.count()}; return {buf.out(), buf.count()};
} }
@ -559,8 +559,8 @@ template <size_t N> class static_format_result {
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0'; *fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
} }
FMT_CONSTEXPR auto str() const -> fmt::string_view { return {data, N - 1}; } auto str() const -> fmt::string_view { return {data, N - 1}; }
FMT_CONSTEXPR auto c_str() const -> const char* { return data; } auto c_str() const -> const char* { return data; }
}; };
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -493,8 +493,8 @@ template <typename OutputIt,
#if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION #if FMT_CLANG_VERSION >= 307 && !FMT_ICC_VERSION
__attribute__((no_sanitize("undefined"))) __attribute__((no_sanitize("undefined")))
#endif #endif
FMT_CONSTEXPR20 inline auto reserve(OutputIt it, size_t n) -> FMT_CONSTEXPR20 inline auto
typename OutputIt::value_type* { reserve(OutputIt it, size_t n) -> typename OutputIt::value_type* {
auto& c = get_container(it); auto& c = get_container(it);
size_t size = c.size(); size_t size = c.size();
c.resize(size + n); c.resize(size + n);
@ -736,8 +736,12 @@ using fast_float_t = conditional_t<sizeof(T) == sizeof(double), double, float>;
template <typename T> template <typename T>
using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>; using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
#endif
// An allocator that uses malloc/free to allow removing dependency on the C++ // An allocator that uses malloc/free to allow removing dependency on the C++
// standard library runtime. std::decay is used for back_inserter to be found by // standard libary runtime. std::decay is used for back_inserter to be found by
// ADL when applied to memory_buffer. // ADL when applied to memory_buffer.
template <typename T> struct allocator : private std::decay<void> { template <typename T> struct allocator : private std::decay<void> {
using value_type = T; using value_type = T;
@ -1307,13 +1311,7 @@ class utf8_to_utf16 {
inline auto str() const -> std::wstring { return {&buffer_[0], size()}; } inline auto str() const -> std::wstring { return {&buffer_[0], size()}; }
}; };
enum class to_utf8_error_policy { abort, replace, wtf }; enum class to_utf8_error_policy { abort, replace };
inline void to_utf8_3bytes(buffer<char>& buf, uint32_t cp) {
buf.push_back(static_cast<char>(0xe0 | (cp >> 12)));
buf.push_back(static_cast<char>(0x80 | ((cp & 0xfff) >> 6)));
buf.push_back(static_cast<char>(0x80 | (cp & 0x3f)));
}
// A converter from UTF-16/UTF-32 (host endian) to UTF-8. // A converter from UTF-16/UTF-32 (host endian) to UTF-8.
template <typename WChar, typename Buffer = memory_buffer> class to_utf8 { template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
@ -1355,13 +1353,8 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
// Handle a surrogate pair. // Handle a surrogate pair.
++p; ++p;
if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) { if (p == s.end() || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
switch (policy) { if (policy == to_utf8_error_policy::abort) return false;
case to_utf8_error_policy::abort: return false; buf.append(string_view("\xEF\xBF\xBD"));
case to_utf8_error_policy::replace:
buf.append(string_view("\xEF\xBF\xBD"));
break;
case to_utf8_error_policy::wtf: to_utf8_3bytes(buf, c); break;
}
--p; --p;
continue; continue;
} }
@ -1373,7 +1366,9 @@ template <typename WChar, typename Buffer = memory_buffer> class to_utf8 {
buf.push_back(static_cast<char>(0xc0 | (c >> 6))); buf.push_back(static_cast<char>(0xc0 | (c >> 6)));
buf.push_back(static_cast<char>(0x80 | (c & 0x3f))); buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) { } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
to_utf8_3bytes(buf, c); buf.push_back(static_cast<char>(0xe0 | (c >> 12)));
buf.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
} else if (c >= 0x10000 && c <= 0x10ffff) { } else if (c >= 0x10000 && c <= 0x10ffff) {
buf.push_back(static_cast<char>(0xf0 | (c >> 18))); buf.push_back(static_cast<char>(0xf0 | (c >> 18)));
buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12))); buf.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
@ -2539,7 +2534,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
auto grouping = Grouping(loc, specs.localized()); auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp); size += grouping.count_separators(exp);
return write_padded<Char, align::right>( return write_padded<Char, align::right>(
out, specs, static_cast<size_t>(size), [&](iterator it) { out, specs, to_unsigned(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s); if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand(it, f.significand, significand_size, exp, it = write_significand(it, f.significand, significand_size, exp,
decimal_point, grouping); decimal_point, grouping);
@ -2555,7 +2550,7 @@ FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt(); bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
size += 1 + (pointy ? 1 : 0) + num_zeros; size += 1 + (pointy ? 1 : 0) + num_zeros;
return write_padded<Char, align::right>( return write_padded<Char, align::right>(
out, specs, static_cast<size_t>(size), [&](iterator it) { out, specs, to_unsigned(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s); if (s != sign::none) *it++ = detail::getsign<Char>(s);
*it++ = Char('0'); *it++ = Char('0');
if (!pointy) return it; if (!pointy) return it;
@ -2599,7 +2594,7 @@ FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
*it++ = Char(exp_char); *it++ = Char(exp_char);
return write_exponent<Char>(exp, it); return write_exponent<Char>(exp, it);
}; };
size_t usize = static_cast<size_t>(size); auto usize = to_unsigned(size);
return specs.width > 0 return specs.width > 0
? write_padded<Char, align::right>(out, specs, usize, write) ? write_padded<Char, align::right>(out, specs, usize, write)
: base_iterator(out, write(reserve(out, usize))); : base_iterator(out, write(reserve(out, usize)));
@ -4253,11 +4248,7 @@ class format_int {
* // A compile-time error because 'd' is an invalid specifier for strings. * // A compile-time error because 'd' is an invalid specifier for strings.
* std::string s = fmt::format(FMT_STRING("{:d}"), "foo"); * std::string s = fmt::format(FMT_STRING("{:d}"), "foo");
*/ */
#if FMT_USE_CONSTEVAL #define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)
# define FMT_STRING(s) s
#else
# define FMT_STRING(s) FMT_STRING_IMPL(s, fmt::detail::compile_string)
#endif // FMT_USE_CONSTEVAL
FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args) FMT_API auto vsystem_error(int error_code, string_view fmt, format_args args)
-> std::system_error; -> std::system_error;

View File

@ -161,6 +161,14 @@ inline auto system_category() noexcept -> const std::error_category& {
} }
#endif // _WIN32 #endif // _WIN32
// std::system is not available on some platforms such as iOS (#2248).
#ifdef __OSX__
template <typename S, typename... Args, typename Char = char_t<S>>
void say(const S& fmt, Args&&... args) {
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
}
#endif
// A buffered file. // A buffered file.
class buffered_file { class buffered_file {
private: private:

View File

@ -38,7 +38,7 @@ namespace detail {
namespace { namespace {
struct file_access_tag {}; struct file_access_tag {};
} // namespace } // namespace
template <typename Tag, typename BufType, FILE* BufType::* FileMemberPtr> template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
class file_access { class file_access {
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; } friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
}; };

View File

@ -84,12 +84,10 @@ namespace detail {
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p, auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) { const std::basic_string<PathChar>& native) {
if constexpr (std::is_same_v<Char, char> && if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
std::is_same_v<PathChar, wchar_t>) { return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
return to_utf8<wchar_t>(native, to_utf8_error_policy::wtf); else
} else {
return p.string<Char>(); return p.string<Char>();
}
} }
template <typename Char, typename PathChar> template <typename Char, typename PathChar>
@ -647,11 +645,6 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
}; };
#endif // __cpp_lib_atomic_flag_test #endif // __cpp_lib_atomic_flag_test
template <typename T> struct is_tuple_like;
template <typename T>
struct is_tuple_like<std::complex<T>> : std::false_type {};
template <typename T, typename Char> struct formatter<std::complex<T>, Char> { template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
private: private:
detail::dynamic_format_specs<Char> specs_; detail::dynamic_format_specs<Char> specs_;

View File

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

View File

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

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

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

View File

@ -90,6 +90,9 @@ TEST(compile_test, format_escape) {
EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc")); EXPECT_EQ("\"abc\" ", fmt::format(FMT_COMPILE("{0:<7?}"), "abc"));
} }
TEST(compile_test, format_wide_string) {
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{}"), 42));
}
TEST(compile_test, format_specs) { TEST(compile_test, format_specs) {
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42));
@ -121,6 +124,7 @@ TEST(compile_test, manual_ordering) {
"true 42 42 foo 0x1234 foo", "true 42 42 foo 0x1234 foo",
fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f, fmt::format(FMT_COMPILE("{0} {1} {2} {3} {4} {5}"), true, 42, 42.0f,
"foo", reinterpret_cast<void*>(0x1234), test_formattable())); "foo", reinterpret_cast<void*>(0x1234), test_formattable()));
EXPECT_EQ(L"42", fmt::format(FMT_COMPILE(L"{0}"), 42));
} }
TEST(compile_test, named) { TEST(compile_test, named) {
@ -129,6 +133,10 @@ TEST(compile_test, named) {
static_assert(std::is_same_v<decltype(runtime_named_field_compiled), static_assert(std::is_same_v<decltype(runtime_named_field_compiled),
fmt::detail::runtime_named_field<char>>); fmt::detail::runtime_named_field<char>>);
EXPECT_EQ("42", fmt::format(FMT_COMPILE("{}"), fmt::arg("arg", 42)));
EXPECT_EQ("41 43", fmt::format(FMT_COMPILE("{} {}"), fmt::arg("arg", 41),
fmt::arg("arg", 43)));
EXPECT_EQ("foobar", EXPECT_EQ("foobar",
fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"), fmt::format(FMT_COMPILE("{a0}{a1}"), fmt::arg("a0", "foo"),
fmt::arg("a1", "bar"))); fmt::arg("a1", "bar")));
@ -310,6 +318,7 @@ TEST(compile_test, compile_format_string_literal) {
using namespace fmt::literals; using namespace fmt::literals;
EXPECT_EQ("", fmt::format(""_cf)); EXPECT_EQ("", fmt::format(""_cf));
EXPECT_EQ("42", fmt::format("{}"_cf, 42)); EXPECT_EQ("42", fmt::format("{}"_cf, 42));
EXPECT_EQ(L"42", fmt::format(L"{}"_cf, 42));
} }
#endif #endif
@ -417,40 +426,6 @@ TEST(compile_time_formatting_test, custom_type) {
TEST(compile_time_formatting_test, multibyte_fill) { TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42)); EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
} }
TEST(compile_time_formatting_test, floating_point) {
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{}"), 0.0f));
EXPECT_EQ("392.500000", test_format<11>(FMT_COMPILE("{0:f}"), 392.5f));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:}"), 0.0));
EXPECT_EQ("0.000000", test_format<9>(FMT_COMPILE("{:f}"), 0.0));
EXPECT_EQ("0", test_format<2>(FMT_COMPILE("{:g}"), 0.0));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:g}"), 392.65));
EXPECT_EQ("392.65", test_format<7>(FMT_COMPILE("{:G}"), 392.65));
EXPECT_EQ("4.9014e+06", test_format<11>(FMT_COMPILE("{:g}"), 4.9014e6));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:f}"), -392.65));
EXPECT_EQ("-392.650000", test_format<12>(FMT_COMPILE("{:F}"), -392.65));
EXPECT_EQ("3.926500e+02", test_format<13>(FMT_COMPILE("{0:e}"), 392.65));
EXPECT_EQ("3.926500E+02", test_format<13>(FMT_COMPILE("{0:E}"), 392.65));
EXPECT_EQ("+0000392.6", test_format<11>(FMT_COMPILE("{0:+010.4g}"), 392.65));
EXPECT_EQ("9223372036854775808.000000",
test_format<27>(FMT_COMPILE("{:f}"), 9223372036854775807.0));
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
EXPECT_EQ("nan", test_format<4>(FMT_COMPILE("{}"), nan));
EXPECT_EQ("+nan", test_format<5>(FMT_COMPILE("{:+}"), nan));
if (std::signbit(-nan))
EXPECT_EQ("-nan", test_format<5>(FMT_COMPILE("{}"), -nan));
else
fmt::print("Warning: compiler doesn't handle negative NaN correctly");
constexpr double inf = std::numeric_limits<double>::infinity();
EXPECT_EQ("inf", test_format<4>(FMT_COMPILE("{}"), inf));
EXPECT_EQ("+inf", test_format<5>(FMT_COMPILE("{:+}"), inf));
EXPECT_EQ("-inf", test_format<5>(FMT_COMPILE("{}"), -inf));
}
#endif #endif
#if FMT_USE_CONSTEXPR_STRING #if FMT_USE_CONSTEXPR_STRING

View File

@ -2036,6 +2036,11 @@ TEST(format_test, unpacked_args) {
6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g')); 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g'));
} }
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr char static_with_null[3] = {'{', '}', '\0'};
static constexpr char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) { TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo"); EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");
EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING("{}"), 42), "42");
@ -2050,12 +2055,19 @@ TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2"); EXPECT_EQ(fmt::format(FMT_STRING("{} {two}"), 1, "two"_a = 2), "1 2");
#endif #endif
static constexpr char format_str[3] = {'{', '}', '\0'}; (void)static_with_null;
(void)format_str; (void)static_no_null;
#ifndef _MSC_VER #ifndef _MSC_VER
EXPECT_EQ(fmt::format(FMT_STRING(format_str), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING(static_with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(static_no_null), 42), "42");
#endif #endif
(void)with_null;
(void)no_null;
#if FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(with_null), 42), "42");
EXPECT_EQ(fmt::format(FMT_STRING(no_null), 42), "42");
#endif
#if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L #if defined(FMT_USE_STRING_VIEW) && FMT_CPLUSPLUS >= 201703L
EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42"); EXPECT_EQ(fmt::format(FMT_STRING(std::string_view("{}")), 42), "42");
#endif #endif

View File

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

View File

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

View File

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

View File

@ -39,12 +39,13 @@ TEST(std_test, path) {
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448" EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")), L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына"); "Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80"); EXPECT_EQ(fmt::format("{}", path(L"\xd800")), "<EFBFBD>");
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xd800 TAIL")), "HEAD <20> TAIL");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xDE00 TAIL")),
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")), "HEAD \xF0\x9F\x98\x80 TAIL");
"[\xED\xA0\xBD\xF0\x9F\x98\x80]"); EXPECT_EQ(fmt::format("{}", path(L"HEAD \xD83D\xD83D\xDE00 TAIL")),
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\""); "HEAD <20>\xF0\x9F\x98\x80 TAIL");
EXPECT_EQ(fmt::format("{:?}", path(L"\xd800")), "\"\\ud800\"");
# endif # endif
} }