# Rspamd Dependency Info
| Name | Version | License | Patched | Notes |
-| --- | --- | --- | --- | --- |
+| --- |---------| --- | --- | --- |
| aho-corasick | ? | LGPL-3.0 | YES | lowercase support |
| cdb | 1.1.0 | Public Domain / CC0 | NO | |
| hiredis | 0.13.3 | BSD-3-Clause | YES | many changes |
| libottery | ? | Public Domain / CC0 | YES | many changes |
| librdns | ? | BSD-2-Clause | YES | |
| libucl | ? | BSD-2-Clause | YES | |
-| replxx | 6d93360 | BSD-2-Clause | YES | libicu usage |
+| replxx | 6d93360 | BSD-2-Clause | YES | libicu usage |
| lua-argparse | 0.7.1 | MIT | NO | |
| lua-bit | 1.0.2 | MIT | YES | build fixes |
| lua-fun | ? | MIT | YES | rspamd text |
| lua-lupa | ? | MIT | NO | |
| lua-tableshape | ae67256 | MIT | NO | |
| mumhash | ? | MIT | NO | |
-| ngx-http-parser | 2.2.0 | MIT | YES | spamc support |
-| Mozilla-PublicSuffix | ? | MIT | NO | |
+| ngx-http-parser | 2.2.0 | MIT | YES | spamc support |
+| Mozilla-PublicSuffix | ? | MIT | NO | |
| snowball | ? | BSD-3-Clause | NO | |
| t1ha | ? | Zlib | NO | |
| uthash | 1.9.8 | BSD | YES | |
| xxhash | 0.8.1 | BSD | NO | |
| zstd | 1.4.5 | BSD | NO | |
| google-ced | 37529e6 | Apache 2 | YES | build fixes |
-| kann | ? | MIT | YES | blas/lapack changes|
+| kann | ? | MIT | YES | blas/lapack changes|
| fpconv | ? | Boost | YES | many changes |
| fastutf8 | ? | MIT | YES | many changes |
| expected | v1.0 | Public Domain / CC0 | NO | |
| robin-hood | 3.9.1 | MIT | NO | |
| frozen | 1.0.1 | Apache 2 | NO | |
-| fmt | 7.1.3 | MIT | NO | |
+| fmt | 8.1.1 | MIT | NO | |
| doctest | 2.4.6 | MIT | NO | |
| function2 | 4.1.0 | Boost | NO | |
#include <algorithm>
#include <chrono>
#include <ctime>
+#include <iterator>
#include <locale>
-#include <sstream>
+#include <ostream>
+#include <type_traits>
#include "format.h"
FMT_BEGIN_NAMESPACE
+// Enable tzset.
+#ifndef FMT_USE_TZSET
+// UWP doesn't provide _tzset.
+# if FMT_HAS_INCLUDE("winapifamily.h")
+# include <winapifamily.h>
+# endif
+# if defined(_WIN32) && (!defined(WINAPI_FAMILY) || \
+ (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+# define FMT_USE_TZSET 1
+# else
+# define FMT_USE_TZSET 0
+# endif
+#endif
+
// Enable safe chrono durations, unless explicitly disabled.
#ifndef FMT_SAFE_DURATION_CAST
# define FMT_SAFE_DURATION_CAST 1
static_assert(T::is_integer, "To must be integral");
// A and B are both signed, or both unsigned.
- if (F::digits <= T::digits) {
+ if (detail::const_check(F::digits <= T::digits)) {
// From fits in To without any problem.
} else {
// From does not always fit in To, resort to a dynamic check.
return {};
}
// From is positive. Can it always fit in To?
- if (F::digits > T::digits &&
+ if (detail::const_check(F::digits > T::digits) &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
}
}
- if (!F::is_signed && T::is_signed && F::digits >= T::digits &&
+ if (detail::const_check(!F::is_signed && T::is_signed &&
+ F::digits >= T::digits) &&
from > static_cast<From>(detail::max_value<To>())) {
ec = 1;
return {};
}
// multiply with Factor::num without overflow or underflow
- if (Factor::num != 1) {
+ if (detail::const_check(Factor::num != 1)) {
constexpr auto max1 = detail::max_value<IntermediateRep>() /
static_cast<IntermediateRep>(Factor::num);
if (count > max1) {
}
// this can't go wrong, right? den>0 is checked earlier.
- if (Factor::den != 1) {
+ if (detail::const_check(Factor::den != 1)) {
using common_t = typename std::common_type<IntermediateRep, intmax_t>::type;
count /= static_cast<common_t>(Factor::den);
}
inline null<> gmtime_r(...) { return null<>(); }
inline null<> gmtime_s(...) { return null<>(); }
-inline auto do_write(const std::tm& time, const std::locale& loc, char format,
- char modifier) -> std::string {
- auto&& os = std::ostringstream();
- os.imbue(loc);
- using iterator = std::ostreambuf_iterator<char>;
- const auto& facet = std::use_facet<std::time_put<char, iterator>>(loc);
- auto end = facet.put(os, os, ' ', &time, format, modifier);
- if (end.failed()) FMT_THROW(format_error("failed to format time"));
- auto str = os.str();
- if (!detail::is_utf8() || loc == std::locale::classic()) return str;
- // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
- // gcc-4.
-#if FMT_MSC_VER != 0 || \
- (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
- // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
- // and newer.
- using code_unit = wchar_t;
+inline const std::locale& get_classic_locale() {
+ static const auto& locale = std::locale::classic();
+ return locale;
+}
+
+template <typename CodeUnit> struct codecvt_result {
+ static constexpr const size_t max_size = 32;
+ CodeUnit buf[max_size];
+ CodeUnit* end;
+};
+template <typename CodeUnit>
+constexpr const size_t codecvt_result<CodeUnit>::max_size;
+
+template <typename CodeUnit>
+void write_codecvt(codecvt_result<CodeUnit>& out, string_view in_buf,
+ const std::locale& loc) {
+ using codecvt = std::codecvt<CodeUnit, char, std::mbstate_t>;
+#if FMT_CLANG_VERSION
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wdeprecated"
+ auto& f = std::use_facet<codecvt>(loc);
+# pragma clang diagnostic pop
#else
- using code_unit = char32_t;
+ auto& f = std::use_facet<codecvt>(loc);
#endif
- auto& f = std::use_facet<std::codecvt<code_unit, char, std::mbstate_t>>(loc);
auto mb = std::mbstate_t();
const char* from_next = nullptr;
- code_unit* to_next = nullptr;
- constexpr size_t buf_size = 32;
- code_unit buf[buf_size] = {};
- auto result = f.in(mb, str.data(), str.data() + str.size(), from_next, buf,
- buf + buf_size, to_next);
+ auto result = f.in(mb, in_buf.begin(), in_buf.end(), from_next,
+ std::begin(out.buf), std::end(out.buf), out.end);
if (result != std::codecvt_base::ok)
FMT_THROW(format_error("failed to format time"));
- str.clear();
- for (code_unit* p = buf; p != to_next; ++p) {
- uint32_t c = static_cast<uint32_t>(*p);
- if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
- // surrogate pair
- ++p;
- if (p == to_next || (c & 0xfc00) != 0xd800 || (*p & 0xfc00) != 0xdc00) {
+}
+
+template <typename OutputIt>
+auto write_encoded_tm_str(OutputIt out, string_view in, const std::locale& loc)
+ -> OutputIt {
+ if (detail::is_utf8() && loc != get_classic_locale()) {
+ // char16_t and char32_t codecvts are broken in MSVC (linkage errors) and
+ // gcc-4.
+#if FMT_MSC_VER != 0 || \
+ (defined(__GLIBCXX__) && !defined(_GLIBCXX_USE_DUAL_ABI))
+ // The _GLIBCXX_USE_DUAL_ABI macro is always defined in libstdc++ from gcc-5
+ // and newer.
+ using code_unit = wchar_t;
+#else
+ using code_unit = char32_t;
+#endif
+
+ using unit_t = codecvt_result<code_unit>;
+ unit_t unit;
+ write_codecvt(unit, in, loc);
+ // In UTF-8 is used one to four one-byte code units.
+ auto&& buf = basic_memory_buffer<char, unit_t::max_size * 4>();
+ for (code_unit* p = unit.buf; p != unit.end; ++p) {
+ uint32_t c = static_cast<uint32_t>(*p);
+ if (sizeof(code_unit) == 2 && c >= 0xd800 && c <= 0xdfff) {
+ // surrogate pair
+ ++p;
+ if (p == unit.end || (c & 0xfc00) != 0xd800 ||
+ (*p & 0xfc00) != 0xdc00) {
+ FMT_THROW(format_error("failed to format time"));
+ }
+ c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
+ }
+ if (c < 0x80) {
+ buf.push_back(static_cast<char>(c));
+ } else if (c < 0x800) {
+ buf.push_back(static_cast<char>(0xc0 | (c >> 6)));
+ buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
+ } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
+ 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) {
+ 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 & 0xfff) >> 6)));
+ buf.push_back(static_cast<char>(0x80 | (c & 0x3f)));
+ } else {
FMT_THROW(format_error("failed to format time"));
}
- c = (c << 10) + static_cast<uint32_t>(*p) - 0x35fdc00;
- }
- if (c < 0x80) {
- str.push_back(static_cast<char>(c));
- } else if (c < 0x800) {
- str.push_back(static_cast<char>(0xc0 | (c >> 6)));
- str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
- } else if ((c >= 0x800 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xffff)) {
- str.push_back(static_cast<char>(0xe0 | (c >> 12)));
- str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
- str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
- } else if (c >= 0x10000 && c <= 0x10ffff) {
- str.push_back(static_cast<char>(0xf0 | (c >> 18)));
- str.push_back(static_cast<char>(0x80 | ((c & 0x3ffff) >> 12)));
- str.push_back(static_cast<char>(0x80 | ((c & 0xfff) >> 6)));
- str.push_back(static_cast<char>(0x80 | (c & 0x3f)));
- } else {
- FMT_THROW(format_error("failed to format time"));
}
+ return copy_str<char>(buf.data(), buf.data() + buf.size(), out);
}
- return str;
+ return copy_str<char>(in.data(), in.data() + in.size(), out);
}
-template <typename OutputIt>
+template <typename Char, typename OutputIt,
+ FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
+ -> OutputIt {
+ codecvt_result<Char> unit;
+ write_codecvt(unit, sv, loc);
+ return copy_str<Char>(unit.buf, unit.end, out);
+}
+
+template <typename Char, typename OutputIt,
+ FMT_ENABLE_IF(std::is_same<Char, char>::value)>
+auto write_tm_str(OutputIt out, string_view sv, const std::locale& loc)
+ -> OutputIt {
+ return write_encoded_tm_str(out, sv, loc);
+}
+
+template <typename Char>
+inline void do_write(buffer<Char>& buf, const std::tm& time,
+ const std::locale& loc, char format, char modifier) {
+ auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
+ auto&& os = std::basic_ostream<Char>(&format_buf);
+ os.imbue(loc);
+ using iterator = std::ostreambuf_iterator<Char>;
+ const auto& facet = std::use_facet<std::time_put<Char, iterator>>(loc);
+ auto end = facet.put(os, os, Char(' '), &time, format, modifier);
+ if (end.failed()) FMT_THROW(format_error("failed to format time"));
+}
+
+template <typename Char, typename OutputIt,
+ FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
+auto write(OutputIt out, const std::tm& time, const std::locale& loc,
+ char format, char modifier = 0) -> OutputIt {
+ auto&& buf = get_buffer<Char>(out);
+ do_write<Char>(buf, time, loc, format, modifier);
+ return buf.out();
+}
+
+template <typename Char, typename OutputIt,
+ FMT_ENABLE_IF(std::is_same<Char, char>::value)>
auto write(OutputIt out, const std::tm& time, const std::locale& loc,
char format, char modifier = 0) -> OutputIt {
- auto str = do_write(time, loc, format, modifier);
- return std::copy(str.begin(), str.end(), out);
+ auto&& buf = basic_memory_buffer<Char>();
+ do_write<char>(buf, time, loc, format, modifier);
+ return write_encoded_tm_str(out, string_view(buf.data(), buf.size()), loc);
}
+
} // namespace detail
FMT_MODULE_EXPORT_BEGIN
FMT_BEGIN_DETAIL_NAMESPACE
-inline size_t strftime(char* str, size_t count, const char* format,
- const std::tm* time) {
- // Assign to a pointer to suppress GCCs -Wformat-nonliteral
- // First assign the nullptr to suppress -Wsuggest-attribute=format
- std::size_t (*strftime)(char*, std::size_t, const char*, const std::tm*) =
- nullptr;
- strftime = std::strftime;
- return strftime(str, count, format, time);
-}
-
-inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format,
- const std::tm* time) {
- // See above
- std::size_t (*wcsftime)(wchar_t*, std::size_t, const wchar_t*,
- const std::tm*) = nullptr;
- wcsftime = std::wcsftime;
- return wcsftime(str, count, format, time);
-}
-
-FMT_END_DETAIL_NAMESPACE
-
-template <typename Char, typename Duration>
-struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
- Char> : formatter<std::tm, Char> {
- FMT_CONSTEXPR formatter() {
- this->specs = {default_specs, sizeof(default_specs) / sizeof(Char)};
- }
-
- template <typename ParseContext>
- FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- auto it = ctx.begin();
- if (it != ctx.end() && *it == ':') ++it;
- auto end = it;
- while (end != ctx.end() && *end != '}') ++end;
- if (end != it) this->specs = {it, detail::to_unsigned(end - it)};
- return end;
- }
-
- template <typename FormatContext>
- auto format(std::chrono::time_point<std::chrono::system_clock> val,
- FormatContext& ctx) -> decltype(ctx.out()) {
- std::tm time = localtime(val);
- return formatter<std::tm, Char>::format(time, ctx);
- }
-
- static constexpr Char default_specs[] = {'%', 'Y', '-', '%', 'm', '-',
- '%', 'd', ' ', '%', 'H', ':',
- '%', 'M', ':', '%', 'S'};
-};
-
-template <typename Char, typename Duration>
-constexpr Char
- formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
- Char>::default_specs[];
-
-template <typename Char> struct formatter<std::tm, Char> {
- template <typename ParseContext>
- FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- auto it = ctx.begin();
- if (it != ctx.end() && *it == ':') ++it;
- auto end = it;
- while (end != ctx.end() && *end != '}') ++end;
- specs = {it, detail::to_unsigned(end - it)};
- return end;
- }
-
- template <typename FormatContext>
- auto format(const std::tm& tm, FormatContext& ctx) const
- -> decltype(ctx.out()) {
- basic_memory_buffer<Char> tm_format;
- tm_format.append(specs.begin(), specs.end());
- // By appending an extra space we can distinguish an empty result that
- // indicates insufficient buffer size from a guaranteed non-empty result
- // https://github.com/fmtlib/fmt/issues/2238
- tm_format.push_back(' ');
- tm_format.push_back('\0');
- basic_memory_buffer<Char> buf;
- size_t start = buf.size();
- for (;;) {
- size_t size = buf.capacity() - start;
- size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm);
- if (count != 0) {
- buf.resize(start + count);
- break;
- }
- const size_t MIN_GROWTH = 10;
- buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH));
- }
- // Remove the extra space.
- return std::copy(buf.begin(), buf.end() - 1, ctx.out());
+// Writes two-digit numbers a, b and c separated by sep to buf.
+// The method by Pavel Novikov based on
+// https://johnnylee-sde.github.io/Fast-unsigned-integer-to-time-string/.
+inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
+ unsigned c, char sep) {
+ unsigned long long digits =
+ a | (b << 24) | (static_cast<unsigned long long>(c) << 48);
+ // Convert each value to BCD.
+ // We have x = a * 10 + b and we want to convert it to BCD y = a * 16 + b.
+ // The difference is
+ // y - x = a * 6
+ // a can be found from x:
+ // a = floor(x / 10)
+ // then
+ // y = x + a * 6 = x + floor(x / 10) * 6
+ // floor(x / 10) is (x * 205) >> 11 (needs 16 bits).
+ digits += (((digits * 205) >> 11) & 0x000f00000f00000f) * 6;
+ // Put low nibbles to high bytes and high nibbles to low bytes.
+ digits = ((digits & 0x00f00000f00000f0) >> 4) |
+ ((digits & 0x000f00000f00000f) << 8);
+ auto usep = static_cast<unsigned long long>(sep);
+ // Add ASCII '0' to each digit byte and insert separators.
+ digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
+
+ constexpr const size_t len = 8;
+ if (const_check(is_big_endian())) {
+ char tmp[len];
+ memcpy(tmp, &digits, len);
+ std::reverse_copy(tmp, tmp + len, buf);
+ } else {
+ memcpy(buf, &digits, len);
}
-
- basic_string_view<Char> specs;
-};
-
-FMT_BEGIN_DETAIL_NAMESPACE
+}
template <typename Period> FMT_CONSTEXPR inline const char* get_units() {
if (std::is_same<Period, std::atto>::value) return "as";
handler.on_text(tab, tab + 1);
break;
}
+ // Year:
+ case 'Y':
+ handler.on_year(numeric_system::standard);
+ break;
+ case 'y':
+ handler.on_short_year(numeric_system::standard);
+ break;
+ case 'C':
+ handler.on_century(numeric_system::standard);
+ break;
+ case 'G':
+ handler.on_iso_week_based_year();
+ break;
+ case 'g':
+ handler.on_iso_week_based_short_year();
+ break;
// Day of the week:
case 'a':
handler.on_abbr_weekday();
break;
// Month:
case 'b':
+ case 'h':
handler.on_abbr_month();
break;
case 'B':
handler.on_full_month();
break;
+ case 'm':
+ handler.on_dec_month(numeric_system::standard);
+ break;
+ // Day of the year/month:
+ case 'U':
+ handler.on_dec0_week_of_year(numeric_system::standard);
+ break;
+ case 'W':
+ handler.on_dec1_week_of_year(numeric_system::standard);
+ break;
+ case 'V':
+ handler.on_iso_week_of_year(numeric_system::standard);
+ break;
+ case 'j':
+ handler.on_day_of_year();
+ break;
+ case 'd':
+ handler.on_day_of_month(numeric_system::standard);
+ break;
+ case 'e':
+ handler.on_day_of_month_space(numeric_system::standard);
+ break;
// Hour, minute, second:
case 'H':
handler.on_24_hour(numeric_system::standard);
if (ptr == end) FMT_THROW(format_error("invalid format"));
c = *ptr++;
switch (c) {
+ case 'Y':
+ handler.on_year(numeric_system::alternative);
+ break;
+ case 'y':
+ handler.on_offset_year();
+ break;
+ case 'C':
+ handler.on_century(numeric_system::alternative);
+ break;
case 'c':
handler.on_datetime(numeric_system::alternative);
break;
if (ptr == end) FMT_THROW(format_error("invalid format"));
c = *ptr++;
switch (c) {
+ case 'y':
+ handler.on_short_year(numeric_system::alternative);
+ break;
+ case 'm':
+ handler.on_dec_month(numeric_system::alternative);
+ break;
+ case 'U':
+ handler.on_dec0_week_of_year(numeric_system::alternative);
+ break;
+ case 'W':
+ handler.on_dec1_week_of_year(numeric_system::alternative);
+ break;
+ case 'V':
+ handler.on_iso_week_of_year(numeric_system::alternative);
+ break;
+ case 'd':
+ handler.on_day_of_month(numeric_system::alternative);
+ break;
+ case 'e':
+ handler.on_day_of_month_space(numeric_system::alternative);
+ break;
case 'w':
handler.on_dec0_weekday(numeric_system::alternative);
break;
FMT_CONSTEXPR void unsupported() {
static_cast<Derived*>(this)->unsupported();
}
+ FMT_CONSTEXPR void on_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_short_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_offset_year() { unsupported(); }
+ FMT_CONSTEXPR void on_century(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_iso_week_based_year() { unsupported(); }
+ FMT_CONSTEXPR void on_iso_week_based_short_year() { unsupported(); }
FMT_CONSTEXPR void on_abbr_weekday() { unsupported(); }
FMT_CONSTEXPR void on_full_weekday() { unsupported(); }
FMT_CONSTEXPR void on_dec0_weekday(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_dec1_weekday(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_abbr_month() { unsupported(); }
FMT_CONSTEXPR void on_full_month() { unsupported(); }
+ FMT_CONSTEXPR void on_dec_month(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_day_of_year() { unsupported(); }
+ FMT_CONSTEXPR void on_day_of_month(numeric_system) { unsupported(); }
+ FMT_CONSTEXPR void on_day_of_month_space(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_24_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_12_hour(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_minute(numeric_system) { unsupported(); }
FMT_CONSTEXPR void on_tz_name() { unsupported(); }
};
+struct tm_format_checker : null_chrono_spec_handler<tm_format_checker> {
+ FMT_NORETURN void unsupported() { FMT_THROW(format_error("no format")); }
+
+ template <typename Char>
+ FMT_CONSTEXPR void on_text(const Char*, const Char*) {}
+ FMT_CONSTEXPR void on_year(numeric_system) {}
+ FMT_CONSTEXPR void on_short_year(numeric_system) {}
+ FMT_CONSTEXPR void on_offset_year() {}
+ FMT_CONSTEXPR void on_century(numeric_system) {}
+ FMT_CONSTEXPR void on_iso_week_based_year() {}
+ FMT_CONSTEXPR void on_iso_week_based_short_year() {}
+ FMT_CONSTEXPR void on_abbr_weekday() {}
+ FMT_CONSTEXPR void on_full_weekday() {}
+ FMT_CONSTEXPR void on_dec0_weekday(numeric_system) {}
+ FMT_CONSTEXPR void on_dec1_weekday(numeric_system) {}
+ FMT_CONSTEXPR void on_abbr_month() {}
+ FMT_CONSTEXPR void on_full_month() {}
+ FMT_CONSTEXPR void on_dec_month(numeric_system) {}
+ FMT_CONSTEXPR void on_dec0_week_of_year(numeric_system) {}
+ FMT_CONSTEXPR void on_dec1_week_of_year(numeric_system) {}
+ FMT_CONSTEXPR void on_iso_week_of_year(numeric_system) {}
+ FMT_CONSTEXPR void on_day_of_year() {}
+ FMT_CONSTEXPR void on_day_of_month(numeric_system) {}
+ FMT_CONSTEXPR void on_day_of_month_space(numeric_system) {}
+ FMT_CONSTEXPR void on_24_hour(numeric_system) {}
+ FMT_CONSTEXPR void on_12_hour(numeric_system) {}
+ FMT_CONSTEXPR void on_minute(numeric_system) {}
+ FMT_CONSTEXPR void on_second(numeric_system) {}
+ FMT_CONSTEXPR void on_datetime(numeric_system) {}
+ FMT_CONSTEXPR void on_loc_date(numeric_system) {}
+ FMT_CONSTEXPR void on_loc_time(numeric_system) {}
+ FMT_CONSTEXPR void on_us_date() {}
+ FMT_CONSTEXPR void on_iso_date() {}
+ FMT_CONSTEXPR void on_12_hour_time() {}
+ FMT_CONSTEXPR void on_24_hour_time() {}
+ FMT_CONSTEXPR void on_iso_time() {}
+ FMT_CONSTEXPR void on_am_pm() {}
+ FMT_CONSTEXPR void on_utc_offset() {}
+ FMT_CONSTEXPR void on_tz_name() {}
+};
+
+inline const char* tm_wday_full_name(int wday) {
+ static constexpr const char* full_name_list[] = {
+ "Sunday", "Monday", "Tuesday", "Wednesday",
+ "Thursday", "Friday", "Saturday"};
+ return wday >= 0 && wday <= 6 ? full_name_list[wday] : "?";
+}
+inline const char* tm_wday_short_name(int wday) {
+ static constexpr const char* short_name_list[] = {"Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"};
+ return wday >= 0 && wday <= 6 ? short_name_list[wday] : "???";
+}
+
+inline const char* tm_mon_full_name(int mon) {
+ static constexpr const char* full_name_list[] = {
+ "January", "February", "March", "April", "May", "June",
+ "July", "August", "September", "October", "November", "December"};
+ return mon >= 0 && mon <= 11 ? full_name_list[mon] : "?";
+}
+inline const char* tm_mon_short_name(int mon) {
+ static constexpr const char* short_name_list[] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
+ };
+ return mon >= 0 && mon <= 11 ? short_name_list[mon] : "???";
+}
+
+template <typename T, typename = void>
+struct has_member_data_tm_gmtoff : std::false_type {};
+template <typename T>
+struct has_member_data_tm_gmtoff<T, void_t<decltype(T::tm_gmtoff)>>
+ : std::true_type {};
+
+template <typename T, typename = void>
+struct has_member_data_tm_zone : std::false_type {};
+template <typename T>
+struct has_member_data_tm_zone<T, void_t<decltype(T::tm_zone)>>
+ : std::true_type {};
+
+#if FMT_USE_TZSET
+inline void tzset_once() {
+ static bool init = []() -> bool {
+ _tzset();
+ return true;
+ }();
+ ignore_unused(init);
+}
+#endif
+
+template <typename OutputIt, typename Char> class tm_writer {
+ private:
+ static constexpr int days_per_week = 7;
+
+ const std::locale& loc_;
+ const bool is_classic_;
+ OutputIt out_;
+ const std::tm& tm_;
+
+ auto tm_sec() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_sec >= 0 && tm_.tm_sec <= 61, "");
+ return tm_.tm_sec;
+ }
+ auto tm_min() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_min >= 0 && tm_.tm_min <= 59, "");
+ return tm_.tm_min;
+ }
+ auto tm_hour() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_hour >= 0 && tm_.tm_hour <= 23, "");
+ return tm_.tm_hour;
+ }
+ auto tm_mday() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_mday >= 1 && tm_.tm_mday <= 31, "");
+ return tm_.tm_mday;
+ }
+ auto tm_mon() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_mon >= 0 && tm_.tm_mon <= 11, "");
+ return tm_.tm_mon;
+ }
+ auto tm_year() const noexcept -> long long { return 1900ll + tm_.tm_year; }
+ auto tm_wday() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_wday >= 0 && tm_.tm_wday <= 6, "");
+ return tm_.tm_wday;
+ }
+ auto tm_yday() const noexcept -> int {
+ FMT_ASSERT(tm_.tm_yday >= 0 && tm_.tm_yday <= 365, "");
+ return tm_.tm_yday;
+ }
+
+ auto tm_hour12() const noexcept -> int {
+ const auto h = tm_hour();
+ const auto z = h < 12 ? h : h - 12;
+ return z == 0 ? 12 : z;
+ }
+
+ // POSIX and the C Standard are unclear or inconsistent about what %C and %y
+ // do if the year is negative or exceeds 9999. Use the convention that %C
+ // concatenated with %y yields the same output as %Y, and that %Y contains at
+ // least 4 characters, with more only if necessary.
+ auto split_year_lower(long long year) const noexcept -> int {
+ auto l = year % 100;
+ if (l < 0) l = -l; // l in [0, 99]
+ return static_cast<int>(l);
+ }
+
+ // Algorithm:
+ // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_the_week_number_from_a_month_and_day_of_the_month_or_ordinal_date
+ auto iso_year_weeks(long long curr_year) const noexcept -> int {
+ const auto prev_year = curr_year - 1;
+ const auto curr_p =
+ (curr_year + curr_year / 4 - curr_year / 100 + curr_year / 400) %
+ days_per_week;
+ const auto prev_p =
+ (prev_year + prev_year / 4 - prev_year / 100 + prev_year / 400) %
+ days_per_week;
+ return 52 + ((curr_p == 4 || prev_p == 3) ? 1 : 0);
+ }
+ auto iso_week_num(int tm_yday, int tm_wday) const noexcept -> int {
+ return (tm_yday + 11 - (tm_wday == 0 ? days_per_week : tm_wday)) /
+ days_per_week;
+ }
+ auto tm_iso_week_year() const noexcept -> long long {
+ const auto year = tm_year();
+ const auto w = iso_week_num(tm_yday(), tm_wday());
+ if (w < 1) return year - 1;
+ if (w > iso_year_weeks(year)) return year + 1;
+ return year;
+ }
+ auto tm_iso_week_of_year() const noexcept -> int {
+ const auto year = tm_year();
+ const auto w = iso_week_num(tm_yday(), tm_wday());
+ if (w < 1) return iso_year_weeks(year - 1);
+ if (w > iso_year_weeks(year)) return 1;
+ return w;
+ }
+
+ void write1(int value) {
+ *out_++ = static_cast<char>('0' + to_unsigned(value) % 10);
+ }
+ void write2(int value) {
+ const char* d = digits2(to_unsigned(value) % 100);
+ *out_++ = *d++;
+ *out_++ = *d;
+ }
+
+ void write_year_extended(long long year) {
+ // At least 4 characters.
+ int width = 4;
+ if (year < 0) {
+ *out_++ = '-';
+ year = 0 - year;
+ --width;
+ }
+ uint32_or_64_or_128_t<long long> n = to_unsigned(year);
+ const int num_digits = count_digits(n);
+ if (width > num_digits) out_ = std::fill_n(out_, width - num_digits, '0');
+ out_ = format_decimal<Char>(out_, n, num_digits).end;
+ }
+ void write_year(long long year) {
+ if (year >= 0 && year < 10000) {
+ write2(static_cast<int>(year / 100));
+ write2(static_cast<int>(year % 100));
+ } else {
+ write_year_extended(year);
+ }
+ }
+
+ void write_utc_offset(long offset) {
+ if (offset < 0) {
+ *out_++ = '-';
+ offset = -offset;
+ } else {
+ *out_++ = '+';
+ }
+ offset /= 60;
+ write2(static_cast<int>(offset / 60));
+ write2(static_cast<int>(offset % 60));
+ }
+ template <typename T, FMT_ENABLE_IF(has_member_data_tm_gmtoff<T>::value)>
+ void format_utc_offset_impl(const T& tm) {
+ write_utc_offset(tm.tm_gmtoff);
+ }
+ template <typename T, FMT_ENABLE_IF(!has_member_data_tm_gmtoff<T>::value)>
+ void format_utc_offset_impl(const T& tm) {
+#if defined(_WIN32) && defined(_UCRT)
+# if FMT_USE_TZSET
+ tzset_once();
+# endif
+ long offset = 0;
+ _get_timezone(&offset);
+ if (tm.tm_isdst) {
+ long dstbias = 0;
+ _get_dstbias(&dstbias);
+ offset += dstbias;
+ }
+ write_utc_offset(-offset);
+#else
+ ignore_unused(tm);
+ format_localized('z');
+#endif
+ }
+
+ template <typename T, FMT_ENABLE_IF(has_member_data_tm_zone<T>::value)>
+ void format_tz_name_impl(const T& tm) {
+ if (is_classic_)
+ out_ = write_tm_str<Char>(out_, tm.tm_zone, loc_);
+ else
+ format_localized('Z');
+ }
+ template <typename T, FMT_ENABLE_IF(!has_member_data_tm_zone<T>::value)>
+ void format_tz_name_impl(const T&) {
+ format_localized('Z');
+ }
+
+ void format_localized(char format, char modifier = 0) {
+ out_ = write<Char>(out_, tm_, loc_, format, modifier);
+ }
+
+ public:
+ tm_writer(const std::locale& loc, OutputIt out, const std::tm& tm)
+ : loc_(loc),
+ is_classic_(loc_ == get_classic_locale()),
+ out_(out),
+ tm_(tm) {}
+
+ OutputIt out() const { return out_; }
+
+ FMT_CONSTEXPR void on_text(const Char* begin, const Char* end) {
+ out_ = copy_str<Char>(begin, end, out_);
+ }
+
+ void on_abbr_weekday() {
+ if (is_classic_)
+ out_ = write(out_, tm_wday_short_name(tm_wday()));
+ else
+ format_localized('a');
+ }
+ void on_full_weekday() {
+ if (is_classic_)
+ out_ = write(out_, tm_wday_full_name(tm_wday()));
+ else
+ format_localized('A');
+ }
+ void on_dec0_weekday(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) return write1(tm_wday());
+ format_localized('w', 'O');
+ }
+ void on_dec1_weekday(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) {
+ auto wday = tm_wday();
+ write1(wday == 0 ? days_per_week : wday);
+ } else {
+ format_localized('u', 'O');
+ }
+ }
+
+ void on_abbr_month() {
+ if (is_classic_)
+ out_ = write(out_, tm_mon_short_name(tm_mon()));
+ else
+ format_localized('b');
+ }
+ void on_full_month() {
+ if (is_classic_)
+ out_ = write(out_, tm_mon_full_name(tm_mon()));
+ else
+ format_localized('B');
+ }
+
+ void on_datetime(numeric_system ns) {
+ if (is_classic_) {
+ on_abbr_weekday();
+ *out_++ = ' ';
+ on_abbr_month();
+ *out_++ = ' ';
+ on_day_of_month_space(numeric_system::standard);
+ *out_++ = ' ';
+ on_iso_time();
+ *out_++ = ' ';
+ on_year(numeric_system::standard);
+ } else {
+ format_localized('c', ns == numeric_system::standard ? '\0' : 'E');
+ }
+ }
+ void on_loc_date(numeric_system ns) {
+ if (is_classic_)
+ on_us_date();
+ else
+ format_localized('x', ns == numeric_system::standard ? '\0' : 'E');
+ }
+ void on_loc_time(numeric_system ns) {
+ if (is_classic_)
+ on_iso_time();
+ else
+ format_localized('X', ns == numeric_system::standard ? '\0' : 'E');
+ }
+ void on_us_date() {
+ char buf[8];
+ write_digit2_separated(buf, to_unsigned(tm_mon() + 1),
+ to_unsigned(tm_mday()),
+ to_unsigned(split_year_lower(tm_year())), '/');
+ out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
+ }
+ void on_iso_date() {
+ auto year = tm_year();
+ char buf[10];
+ size_t offset = 0;
+ if (year >= 0 && year < 10000) {
+ copy2(buf, digits2(to_unsigned(year / 100)));
+ } else {
+ offset = 4;
+ write_year_extended(year);
+ year = 0;
+ }
+ write_digit2_separated(buf + 2, static_cast<unsigned>(year % 100),
+ to_unsigned(tm_mon() + 1), to_unsigned(tm_mday()),
+ '-');
+ out_ = copy_str<Char>(std::begin(buf) + offset, std::end(buf), out_);
+ }
+
+ void on_utc_offset() { format_utc_offset_impl(tm_); }
+ void on_tz_name() { format_tz_name_impl(tm_); }
+
+ void on_year(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard)
+ return write_year(tm_year());
+ format_localized('Y', 'E');
+ }
+ void on_short_year(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard)
+ return write2(split_year_lower(tm_year()));
+ format_localized('y', 'O');
+ }
+ void on_offset_year() {
+ if (is_classic_) return write2(split_year_lower(tm_year()));
+ format_localized('y', 'E');
+ }
+
+ void on_century(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) {
+ auto year = tm_year();
+ auto upper = year / 100;
+ if (year >= -99 && year < 0) {
+ // Zero upper on negative year.
+ *out_++ = '-';
+ *out_++ = '0';
+ } else if (upper >= 0 && upper < 100) {
+ write2(static_cast<int>(upper));
+ } else {
+ out_ = write<Char>(out_, upper);
+ }
+ } else {
+ format_localized('C', 'E');
+ }
+ }
+
+ void on_dec_month(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard)
+ return write2(tm_mon() + 1);
+ format_localized('m', 'O');
+ }
+
+ void on_dec0_week_of_year(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard)
+ return write2((tm_yday() + days_per_week - tm_wday()) / days_per_week);
+ format_localized('U', 'O');
+ }
+ void on_dec1_week_of_year(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) {
+ auto wday = tm_wday();
+ write2((tm_yday() + days_per_week -
+ (wday == 0 ? (days_per_week - 1) : (wday - 1))) /
+ days_per_week);
+ } else {
+ format_localized('W', 'O');
+ }
+ }
+ void on_iso_week_of_year(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard)
+ return write2(tm_iso_week_of_year());
+ format_localized('V', 'O');
+ }
+
+ void on_iso_week_based_year() { write_year(tm_iso_week_year()); }
+ void on_iso_week_based_short_year() {
+ write2(split_year_lower(tm_iso_week_year()));
+ }
+
+ void on_day_of_year() {
+ auto yday = tm_yday() + 1;
+ write1(yday / 100);
+ write2(yday % 100);
+ }
+ void on_day_of_month(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) return write2(tm_mday());
+ format_localized('d', 'O');
+ }
+ void on_day_of_month_space(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) {
+ auto mday = to_unsigned(tm_mday()) % 100;
+ const char* d2 = digits2(mday);
+ *out_++ = mday < 10 ? ' ' : d2[0];
+ *out_++ = d2[1];
+ } else {
+ format_localized('e', 'O');
+ }
+ }
+
+ void on_24_hour(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) return write2(tm_hour());
+ format_localized('H', 'O');
+ }
+ void on_12_hour(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard)
+ return write2(tm_hour12());
+ format_localized('I', 'O');
+ }
+ void on_minute(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) return write2(tm_min());
+ format_localized('M', 'O');
+ }
+ void on_second(numeric_system ns) {
+ if (is_classic_ || ns == numeric_system::standard) return write2(tm_sec());
+ format_localized('S', 'O');
+ }
+
+ void on_12_hour_time() {
+ if (is_classic_) {
+ char buf[8];
+ write_digit2_separated(buf, to_unsigned(tm_hour12()),
+ to_unsigned(tm_min()), to_unsigned(tm_sec()), ':');
+ out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
+ *out_++ = ' ';
+ on_am_pm();
+ } else {
+ format_localized('r');
+ }
+ }
+ void on_24_hour_time() {
+ write2(tm_hour());
+ *out_++ = ':';
+ write2(tm_min());
+ }
+ void on_iso_time() {
+ char buf[8];
+ write_digit2_separated(buf, to_unsigned(tm_hour()), to_unsigned(tm_min()),
+ to_unsigned(tm_sec()), ':');
+ out_ = copy_str<Char>(std::begin(buf), std::end(buf), out_);
+ }
+
+ void on_am_pm() {
+ if (is_classic_) {
+ *out_++ = tm_hour() < 12 ? 'A' : 'P';
+ *out_++ = 'M';
+ } else {
+ format_localized('p');
+ }
+ }
+
+ // These apply to chrono durations but not tm.
+ void on_duration_value() {}
+ void on_duration_unit() {}
+};
+
struct chrono_format_checker : null_chrono_spec_handler<chrono_format_checker> {
FMT_NORETURN void unsupported() { FMT_THROW(format_error("no date")); }
inline bool isfinite(T) {
return true;
}
-template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
-inline bool isfinite(T value) {
- return std::isfinite(value);
-}
-// Converts value to int and checks that it's in the range [0, upper).
-template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
-inline int to_nonnegative_int(T value, int upper) {
+// Converts value to Int and checks that it's in the range [0, upper).
+template <typename T, typename Int, FMT_ENABLE_IF(std::is_integral<T>::value)>
+inline Int to_nonnegative_int(T value, Int upper) {
FMT_ASSERT(value >= 0 && to_unsigned(value) <= to_unsigned(upper),
"invalid value");
(void)upper;
- return static_cast<int>(value);
+ return static_cast<Int>(value);
}
-template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
-inline int to_nonnegative_int(T value, int upper) {
- FMT_ASSERT(
- std::isnan(value) || (value >= 0 && value <= static_cast<T>(upper)),
- "invalid value");
- (void)upper;
- return static_cast<int>(value);
+template <typename T, typename Int, FMT_ENABLE_IF(!std::is_integral<T>::value)>
+inline Int to_nonnegative_int(T value, Int upper) {
+ if (value < 0 || value > static_cast<T>(upper))
+ FMT_THROW(format_error("invalid value"));
+ return static_cast<Int>(value);
}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
#endif
}
-template <typename Rep, typename Period,
- FMT_ENABLE_IF(std::is_floating_point<Rep>::value)>
-inline std::chrono::duration<Rep, std::milli> get_milliseconds(
+// Returns the number of fractional digits in the range [0, 18] according to the
+// C++20 spec. If more than 18 fractional digits are required then returns 6 for
+// microseconds precision.
+constexpr int count_fractional_digits(long long num, long long den, int n = 0) {
+ return num % den == 0
+ ? n
+ : (n > 18 ? 6 : count_fractional_digits(num * 10, den, n + 1));
+}
+
+constexpr long long pow10(std::uint32_t n) {
+ return n == 0 ? 1 : 10 * pow10(n - 1);
+}
+
+template <class Rep, class Period,
+ FMT_ENABLE_IF(std::numeric_limits<Rep>::is_signed)>
+constexpr std::chrono::duration<Rep, Period> abs(
+ std::chrono::duration<Rep, Period> d) {
+ // We need to compare the duration using the count() method directly
+ // due to a compiler bug in clang-11 regarding the spaceship operator,
+ // when -Wzero-as-null-pointer-constant is enabled.
+ // In clang-12 the bug has been fixed. See
+ // https://bugs.llvm.org/show_bug.cgi?id=46235 and the reproducible example:
+ // https://www.godbolt.org/z/Knbb5joYx.
+ return d.count() >= d.zero().count() ? d : -d;
+}
+
+template <class Rep, class Period,
+ FMT_ENABLE_IF(!std::numeric_limits<Rep>::is_signed)>
+constexpr std::chrono::duration<Rep, Period> abs(
std::chrono::duration<Rep, Period> d) {
- using common_type = typename std::common_type<Rep, std::intmax_t>::type;
- auto ms = mod(d.count() * static_cast<common_type>(Period::num) /
- static_cast<common_type>(Period::den) * 1000,
- 1000);
- return std::chrono::duration<Rep, std::milli>(static_cast<Rep>(ms));
+ return d;
}
template <typename Char, typename Rep, typename OutputIt,
OutputIt format_duration_value(OutputIt out, Rep val, int precision) {
auto specs = basic_format_specs<Char>();
specs.precision = precision;
- specs.type = precision > 0 ? 'f' : 'g';
+ specs.type = precision >= 0 ? presentation_type::fixed_lower
+ : presentation_type::general_lower;
return write<Char>(out, val, specs);
}
return out;
}
+class get_locale {
+ private:
+ union {
+ std::locale locale_;
+ };
+ bool has_locale_ = false;
+
+ public:
+ get_locale(bool localized, locale_ref loc) : has_locale_(localized) {
+ if (localized)
+ ::new (&locale_) std::locale(loc.template get<std::locale>());
+ }
+ ~get_locale() {
+ if (has_locale_) locale_.~locale();
+ }
+ operator const std::locale&() const {
+ return has_locale_ ? locale_ : get_classic_locale();
+ }
+};
+
template <typename FormatContext, typename OutputIt, typename Rep,
typename Period>
struct chrono_formatter {
bool negative;
using char_type = typename FormatContext::char_type;
+ using tm_writer_type = tm_writer<OutputIt, char_type>;
- explicit chrono_formatter(FormatContext& ctx, OutputIt o,
- std::chrono::duration<Rep, Period> d)
+ chrono_formatter(FormatContext& ctx, OutputIt o,
+ std::chrono::duration<Rep, Period> d)
: context(ctx),
out(o),
val(static_cast<rep>(d.count())),
out = format_decimal<char_type>(out, n, num_digits).end;
}
+ template <class Duration> void write_fractional_seconds(Duration d) {
+ constexpr auto num_fractional_digits =
+ count_fractional_digits(Duration::period::num, Duration::period::den);
+
+ using subsecond_precision = std::chrono::duration<
+ typename std::common_type<typename Duration::rep,
+ std::chrono::seconds::rep>::type,
+ std::ratio<1, detail::pow10(num_fractional_digits)>>;
+ if (std::ratio_less<typename subsecond_precision::period,
+ std::chrono::seconds::period>::value) {
+ *out++ = '.';
+ // Don't convert long double to integer seconds to avoid overflow.
+ using sec = conditional_t<
+ std::is_same<typename Duration::rep, long double>::value,
+ std::chrono::duration<long double>, std::chrono::seconds>;
+ auto fractional = detail::abs(d) - std::chrono::duration_cast<sec>(d);
+ const auto subseconds =
+ std::chrono::treat_as_floating_point<
+ typename subsecond_precision::rep>::value
+ ? fractional.count()
+ : std::chrono::duration_cast<subsecond_precision>(fractional)
+ .count();
+ uint32_or_64_or_128_t<long long> n =
+ to_unsigned(to_nonnegative_int(subseconds, max_value<long long>()));
+ int num_digits = detail::count_digits(n);
+ if (num_fractional_digits > num_digits)
+ out = std::fill_n(out, num_fractional_digits - num_digits, '0');
+ out = format_decimal<char_type>(out, n, num_digits).end;
+ }
+ }
+
void write_nan() { std::copy_n("nan", 3, out); }
void write_pinf() { std::copy_n("inf", 3, out); }
void write_ninf() { std::copy_n("-inf", 4, out); }
- void format_localized(const tm& time, char format, char modifier = 0) {
+ template <typename Callback, typename... Args>
+ void format_tm(const tm& time, Callback cb, Args... args) {
if (isnan(val)) return write_nan();
- const auto& loc = localized ? context.locale().template get<std::locale>()
- : std::locale::classic();
- out = detail::write(out, time, loc, format, modifier);
+ get_locale loc(localized, context.locale());
+ auto w = tm_writer_type(loc, out, time);
+ (w.*cb)(args...);
+ out = w.out();
}
void on_text(const char_type* begin, const char_type* end) {
void on_iso_date() {}
void on_utc_offset() {}
void on_tz_name() {}
+ void on_year(numeric_system) {}
+ void on_short_year(numeric_system) {}
+ void on_offset_year() {}
+ void on_century(numeric_system) {}
+ void on_iso_week_based_year() {}
+ void on_iso_week_based_short_year() {}
+ void on_dec_month(numeric_system) {}
+ void on_dec0_week_of_year(numeric_system) {}
+ void on_dec1_week_of_year(numeric_system) {}
+ void on_iso_week_of_year(numeric_system) {}
+ void on_day_of_year() {}
+ void on_day_of_month(numeric_system) {}
+ void on_day_of_month_space(numeric_system) {}
void on_24_hour(numeric_system ns) {
if (handle_nan_inf()) return;
if (ns == numeric_system::standard) return write(hour(), 2);
auto time = tm();
time.tm_hour = to_nonnegative_int(hour(), 24);
- format_localized(time, 'H', 'O');
+ format_tm(time, &tm_writer_type::on_24_hour, ns);
}
void on_12_hour(numeric_system ns) {
if (ns == numeric_system::standard) return write(hour12(), 2);
auto time = tm();
time.tm_hour = to_nonnegative_int(hour12(), 12);
- format_localized(time, 'I', 'O');
+ format_tm(time, &tm_writer_type::on_12_hour, ns);
}
void on_minute(numeric_system ns) {
if (ns == numeric_system::standard) return write(minute(), 2);
auto time = tm();
time.tm_min = to_nonnegative_int(minute(), 60);
- format_localized(time, 'M', 'O');
+ format_tm(time, &tm_writer_type::on_minute, ns);
}
void on_second(numeric_system ns) {
if (ns == numeric_system::standard) {
write(second(), 2);
-#if FMT_SAFE_DURATION_CAST
- // convert rep->Rep
- using duration_rep = std::chrono::duration<rep, Period>;
- using duration_Rep = std::chrono::duration<Rep, Period>;
- auto tmpval = fmt_safe_duration_cast<duration_Rep>(duration_rep{val});
-#else
- auto tmpval = std::chrono::duration<Rep, Period>(val);
-#endif
- auto ms = get_milliseconds(tmpval);
- if (ms != std::chrono::milliseconds(0)) {
- *out++ = '.';
- write(ms.count(), 3);
- }
+ write_fractional_seconds(std::chrono::duration<rep, Period>{val});
return;
}
auto time = tm();
time.tm_sec = to_nonnegative_int(second(), 60);
- format_localized(time, 'S', 'O');
+ format_tm(time, &tm_writer_type::on_second, ns);
}
void on_12_hour_time() {
if (handle_nan_inf()) return;
- format_localized(time(), 'r');
+ format_tm(time(), &tm_writer_type::on_12_hour_time);
}
void on_24_hour_time() {
on_24_hour_time();
*out++ = ':';
if (handle_nan_inf()) return;
- write(second(), 2);
+ on_second(numeric_system::standard);
}
void on_am_pm() {
if (handle_nan_inf()) return;
- format_localized(time(), 'p');
+ format_tm(time(), &tm_writer_type::on_am_pm);
}
void on_duration_value() {
: value(static_cast<unsigned char>(wd != 7 ? wd : 0)) {}
constexpr unsigned c_encoding() const noexcept { return value; }
};
+
+class year_month_day {};
#endif
// A rudimentary weekday formatter.
-template <> struct formatter<weekday> {
+template <typename Char> struct formatter<weekday, Char> {
private:
bool localized = false;
public:
- FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> decltype(ctx.begin()) {
+ FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
+ -> decltype(ctx.begin()) {
auto begin = ctx.begin(), end = ctx.end();
if (begin != end && *begin == 'L') {
++begin;
return begin;
}
- auto format(weekday wd, format_context& ctx) -> decltype(ctx.out()) {
+ template <typename FormatContext>
+ auto format(weekday wd, FormatContext& ctx) const -> decltype(ctx.out()) {
auto time = std::tm();
time.tm_wday = static_cast<int>(wd.c_encoding());
- const auto& loc = localized ? ctx.locale().template get<std::locale>()
- : std::locale::classic();
- return detail::write(ctx.out(), time, loc, 'a');
+ detail::get_locale loc(localized, ctx.locale());
+ auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), time);
+ w.on_abbr_weekday();
+ return w.out();
}
};
++begin;
localized = true;
}
- end = parse_chrono_format(begin, end, detail::chrono_format_checker());
+ end = detail::parse_chrono_format(begin, end,
+ detail::chrono_format_checker());
return {begin, end};
}
}
};
+template <typename Char, typename Duration>
+struct formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
+ Char> : formatter<std::tm, Char> {
+ FMT_CONSTEXPR formatter() {
+ this->do_parse(default_specs,
+ default_specs + sizeof(default_specs) / sizeof(Char));
+ }
+
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ return this->do_parse(ctx.begin(), ctx.end(), true);
+ }
+
+ template <typename FormatContext>
+ auto format(std::chrono::time_point<std::chrono::system_clock> val,
+ FormatContext& ctx) const -> decltype(ctx.out()) {
+ return formatter<std::tm, Char>::format(localtime(val), ctx);
+ }
+
+ static constexpr const Char default_specs[] = {'%', 'F', ' ', '%', 'T'};
+};
+
+template <typename Char, typename Duration>
+constexpr const Char
+ formatter<std::chrono::time_point<std::chrono::system_clock, Duration>,
+ Char>::default_specs[];
+
+template <typename Char> struct formatter<std::tm, Char> {
+ private:
+ enum class spec {
+ unknown,
+ year_month_day,
+ hh_mm_ss,
+ };
+ spec spec_ = spec::unknown;
+ basic_string_view<Char> specs;
+
+ protected:
+ template <typename It>
+ FMT_CONSTEXPR auto do_parse(It begin, It end, bool with_default = false)
+ -> It {
+ if (begin != end && *begin == ':') ++begin;
+ end = detail::parse_chrono_format(begin, end, detail::tm_format_checker());
+ if (!with_default || end != begin)
+ specs = {begin, detail::to_unsigned(end - begin)};
+ // basic_string_view<>::compare isn't constexpr before C++17.
+ if (specs.size() == 2 && specs[0] == Char('%')) {
+ if (specs[1] == Char('F'))
+ spec_ = spec::year_month_day;
+ else if (specs[1] == Char('T'))
+ spec_ = spec::hh_mm_ss;
+ }
+ return end;
+ }
+
+ public:
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ return this->do_parse(ctx.begin(), ctx.end());
+ }
+
+ template <typename FormatContext>
+ auto format(const std::tm& tm, FormatContext& ctx) const
+ -> decltype(ctx.out()) {
+ const auto loc_ref = ctx.locale();
+ detail::get_locale loc(static_cast<bool>(loc_ref), loc_ref);
+ auto w = detail::tm_writer<decltype(ctx.out()), Char>(loc, ctx.out(), tm);
+ if (spec_ == spec::year_month_day)
+ w.on_iso_date();
+ else if (spec_ == spec::hh_mm_ss)
+ w.on_iso_time();
+ else
+ detail::parse_chrono_format(specs.begin(), specs.end(), w);
+ return w.out();
+ }
+};
+
FMT_MODULE_EXPORT_END
FMT_END_NAMESPACE
enum class emphasis : uint8_t {
bold = 1,
- italic = 1 << 1,
- underline = 1 << 2,
- strikethrough = 1 << 3
+ faint = 1 << 1,
+ italic = 1 << 2,
+ underline = 1 << 3,
+ blink = 1 << 4,
+ reverse = 1 << 5,
+ conceal = 1 << 6,
+ strikethrough = 1 << 7,
};
// rgb is a struct for red, green and blue colors.
buffer[19] = static_cast<Char>(0);
}
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
- uint8_t em_codes[4] = {};
- uint8_t em_bits = static_cast<uint8_t>(em);
- if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
- if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
- if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
- if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
- em_codes[3] = 9;
+ uint8_t em_codes[num_emphases] = {};
+ if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
+ if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
+ if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
+ if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
+ if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
+ if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
+ if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
+ if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
size_t index = 0;
- for (int i = 0; i < 4; ++i) {
+ for (size_t i = 0; i < num_emphases; ++i) {
if (!em_codes[i]) continue;
buffer[index++] = static_cast<Char>('\x1b');
buffer[index++] = static_cast<Char>('[');
}
private:
- Char buffer[7u + 3u * 4u + 1u];
+ static constexpr size_t num_emphases = 8;
+ Char buffer[7u + 3u * num_emphases + 1u];
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
char delimiter) FMT_NOEXCEPT {
out[2] = static_cast<Char>('0' + c % 10);
out[3] = static_cast<Char>(delimiter);
}
+ static FMT_CONSTEXPR bool has_emphasis(emphasis em,
+ emphasis mask) FMT_NOEXCEPT {
+ return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
+ }
};
template <typename Char>
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
\endrst
*/
-#ifdef __cpp_if_constexpr
+#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
# define FMT_COMPILE(s) \
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
#else
return value;
}
-#ifdef __cpp_if_constexpr
+#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename... Args> struct type_list {};
// Returns a reference to the argument at index N from [first, rest...].
if constexpr (N == 0)
return first;
else
- return get<N - 1>(rest...);
+ return detail::get<N - 1>(rest...);
}
template <typename Char, typename... Args>
template <int N, typename> struct get_type_impl;
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
- using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
+ using type =
+ remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
};
template <int N, typename T>
// This ensures that the argument type is convertible to `const T&`.
template <typename T, int N, typename... Args>
constexpr const T& get_arg_checked(const Args&... args) {
- const auto& arg = get<N>(args...);
+ const auto& arg = detail::get<N>(args...);
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
return arg.value;
} else {
constexpr OutputIt format(OutputIt out, const Args&... args) const {
bool found = (try_format_argument(out, name, args) || ...);
if (!found) {
- throw format_error("argument with specified name is not found");
+ FMT_THROW(format_error("argument with specified name is not found"));
}
return out;
}
return 0;
}
- constexpr void on_error(const char* message) { throw format_error(message); }
+ constexpr void on_error(const char* message) {
+ FMT_THROW(format_error(message));
+ }
};
template <typename Char> struct parse_arg_id_result {
constexpr auto str = basic_string_view<char_type>(format_str);
if constexpr (str[POS] == '{') {
if constexpr (POS + 1 == str.size())
- throw format_error("unmatched '{' in format string");
+ FMT_THROW(format_error("unmatched '{' in format string"));
if constexpr (str[POS + 1] == '{') {
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
}
} else if constexpr (str[POS] == '}') {
if constexpr (POS + 1 == str.size())
- throw format_error("unmatched '}' in format string");
+ FMT_THROW(format_error("unmatched '}' in format string"));
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
} else {
constexpr auto end = parse_text(str, POS + 1);
return result;
}
}
-#endif // __cpp_if_constexpr
+#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
} // namespace detail
FMT_MODULE_EXPORT_BEGIN
-#ifdef __cpp_if_constexpr
+#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
#ifndef FMT_CORE_H_
#define FMT_CORE_H_
-#include <cstdio> // std::FILE
+#include <cstddef> // std::byte
+#include <cstdio> // std::FILE
#include <cstring>
#include <iterator>
#include <limits>
#include <type_traits>
// The fmt library version in the form major * 10000 + minor * 100 + patch.
-#define FMT_VERSION 80001
+#define FMT_VERSION 80101
-#ifdef __clang__
+#if defined(__clang__) && !defined(__ibmxl__)
# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__)
#else
# define FMT_CLANG_VERSION 0
#endif
-#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER)
+#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \
+ !defined(__NVCOMPILER)
# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
-# define FMT_GCC_PRAGMA(arg) _Pragma(arg)
#else
# define FMT_GCC_VERSION 0
-# define FMT_GCC_PRAGMA(arg)
#endif
-#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__)
-# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION
-#else
-# define FMT_HAS_GXX_CXX11 0
+#ifndef FMT_GCC_PRAGMA
+// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884.
+# if FMT_GCC_VERSION >= 504
+# define FMT_GCC_PRAGMA(arg) _Pragma(arg)
+# else
+# define FMT_GCC_PRAGMA(arg)
+# endif
#endif
-#if defined(__INTEL_COMPILER)
+#ifdef __ICL
+# define FMT_ICC_VERSION __ICL
+#elif defined(__INTEL_COMPILER)
# define FMT_ICC_VERSION __INTEL_COMPILER
#else
# define FMT_ICC_VERSION 0
# define FMT_HAS_CPP_ATTRIBUTE(x) 0
#endif
+#ifdef _MSVC_LANG
+# define FMT_CPLUSPLUS _MSVC_LANG
+#else
+# define FMT_CPLUSPLUS __cplusplus
+#endif
+
#define FMT_HAS_CPP14_ATTRIBUTE(attribute) \
- (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+ (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute))
#define FMT_HAS_CPP17_ATTRIBUTE(attribute) \
- (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))
+ (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute))
// Check if relaxed C++14 constexpr is supported.
// GCC doesn't allow throw in constexpr until version 6 (bug 67371).
#ifndef FMT_USE_CONSTEXPR
# define FMT_USE_CONSTEXPR \
- (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \
+ (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1912 || \
(FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \
!FMT_NVCC && !FMT_ICC_VERSION
#endif
# define FMT_CONSTEXPR_DECL
#endif
+#if ((__cplusplus >= 202002L) && \
+ (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \
+ (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
+# define FMT_CONSTEXPR20 constexpr
+#else
+# define FMT_CONSTEXPR20
+#endif
+
// Check if constexpr std::char_traits<>::compare,length is supported.
#if defined(__GLIBCXX__)
# if __cplusplus >= 201703L && defined(_GLIBCXX_RELEASE) && \
# define FMT_CONSTEXPR_CHAR_TRAITS
#endif
-#ifndef FMT_OVERRIDE
-# if FMT_HAS_FEATURE(cxx_override_control) || \
- (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
-# define FMT_OVERRIDE override
-# else
-# define FMT_OVERRIDE
-# endif
-#endif
-
// Check if exceptions are disabled.
#ifndef FMT_EXCEPTIONS
# if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \
#endif
#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \
- (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900
+ FMT_GCC_VERSION >= 408 || FMT_MSC_VER >= 1900
# define FMT_DETECTED_NOEXCEPT noexcept
# define FMT_HAS_CXX11_NOEXCEPT 1
#else
# define FMT_NORETURN
#endif
-#ifndef FMT_MAYBE_UNUSED
-# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused)
-# define FMT_MAYBE_UNUSED [[maybe_unused]]
-# else
-# define FMT_MAYBE_UNUSED
-# endif
-#endif
-
#if __cplusplus == 201103L || __cplusplus == 201402L
# if defined(__INTEL_COMPILER) || defined(__PGI)
# define FMT_FALLTHROUGH
# else
# define FMT_FALLTHROUGH
# endif
-#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough) || \
- (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
+#elif FMT_HAS_CPP17_ATTRIBUTE(fallthrough)
# define FMT_FALLTHROUGH [[fallthrough]]
#else
# define FMT_FALLTHROUGH
#endif
+#ifndef FMT_NODISCARD
+# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard)
+# define FMT_NODISCARD [[nodiscard]]
+# else
+# define FMT_NODISCARD
+# endif
+#endif
+
#ifndef FMT_USE_FLOAT
# define FMT_USE_FLOAT 1
#endif
# endif
#endif
-#ifndef FMT_USE_INLINE_NAMESPACES
-# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \
- (FMT_MSC_VER >= 1900 && (!defined(_MANAGED) || !_MANAGED))
-# define FMT_USE_INLINE_NAMESPACES 1
+#ifndef FMT_DEPRECATED
+# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
+# define FMT_DEPRECATED [[deprecated]]
# else
-# define FMT_USE_INLINE_NAMESPACES 0
+# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
+# define FMT_DEPRECATED __attribute__((deprecated))
+# elif FMT_MSC_VER
+# define FMT_DEPRECATED __declspec(deprecated)
+# else
+# define FMT_DEPRECATED /* deprecated */
+# endif
# endif
#endif
#ifndef FMT_BEGIN_NAMESPACE
-# if FMT_USE_INLINE_NAMESPACES
-# define FMT_INLINE_NAMESPACE inline namespace
-# define FMT_END_NAMESPACE \
- } \
- }
-# else
-# define FMT_INLINE_NAMESPACE namespace
-# define FMT_END_NAMESPACE \
- } \
- using namespace v8; \
- }
-# endif
# define FMT_BEGIN_NAMESPACE \
namespace fmt { \
- FMT_INLINE_NAMESPACE v8 {
+ inline namespace v8 {
+# define FMT_END_NAMESPACE \
+ } \
+ }
#endif
#ifndef FMT_MODULE_EXPORT
# define FMT_API
#endif
-#if FMT_GCC_VERSION
-# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
-#else
-# define FMT_GCC_VISIBILITY_HIDDEN
-#endif
-
// libc++ supports string_view in pre-c++17.
#if (FMT_HAS_INCLUDE(<string_view>) && \
(__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \
#endif
#ifndef FMT_CONSTEVAL
-# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \
- __cplusplus > 201703L) || \
- (defined(__cpp_consteval) && \
- !FMT_MSC_VER) // consteval is broken in MSVC.
+# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \
+ __cplusplus > 201703L && !defined(__apple_build_version__)) || \
+ (defined(__cpp_consteval) && \
+ (!FMT_MSC_VER || _MSC_FULL_VER >= 193030704))
+// consteval is broken in MSVC before VS2022 and Apple clang 13.
# define FMT_CONSTEVAL consteval
# define FMT_HAS_CONSTEVAL
# else
template <typename T>
using remove_reference_t = typename std::remove_reference<T>::type;
template <typename T>
+using remove_const_t = typename std::remove_const<T>::type;
+template <typename T>
using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type;
template <typename T> struct type_identity { using type = T; };
template <typename T> using type_identity_t = typename type_identity<T>::type;
constexpr monostate() {}
};
-// Suppress "unused variable" warnings with the method described in
-// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
-// (void)var does not work on many Intel compilers.
-template <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {}
-
// An enable_if helper to be used in template parameters which results in much
// shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed
// to workaround a bug in MSVC 2019 (see #1140 and #1186).
FMT_BEGIN_DETAIL_NAMESPACE
-constexpr FMT_INLINE auto is_constant_evaluated() FMT_NOEXCEPT -> bool {
+// Suppress "unused variable" warnings with the method described in
+// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/.
+// (void)var does not work on many Intel compilers.
+template <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {}
+
+constexpr FMT_INLINE auto is_constant_evaluated(bool default_value = false)
+ FMT_NOEXCEPT -> bool {
#ifdef __cpp_lib_is_constant_evaluated
+ ignore_unused(default_value);
return std::is_constant_evaluated();
#else
- return false;
+ return default_value;
#endif
}
// A function to suppress "conditional expression is constant" warnings.
-template <typename T> constexpr auto const_check(T value) -> T { return value; }
+template <typename T> constexpr FMT_INLINE auto const_check(T value) -> T {
+ return value;
+}
FMT_NORETURN FMT_API void assert_fail(const char* file, int line,
const char* message);
# ifdef NDEBUG
// FMT_ASSERT is not empty to avoid -Werror=empty-body.
# define FMT_ASSERT(condition, message) \
- ::fmt::ignore_unused((condition), (message))
+ ::fmt::detail::ignore_unused((condition), (message))
# else
# define FMT_ASSERT(condition, message) \
((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \
# endif
#endif
+#ifdef __cpp_lib_byte
+using byte = std::byte;
+#else
+enum class byte : unsigned char {};
+#endif
+
#if defined(FMT_USE_STRING_VIEW)
template <typename Char> using std_string_view = std::basic_string_view<Char>;
#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW)
*/
FMT_CONSTEXPR_CHAR_TRAITS
FMT_INLINE
- basic_string_view(const Char* s) : data_(s) {
- if (detail::const_check(std::is_same<Char, char>::value &&
- !detail::is_constant_evaluated()))
- size_ = std::strlen(reinterpret_cast<const char*>(s));
- else
- size_ = std::char_traits<Char>::length(s);
- }
+ basic_string_view(const Char* s)
+ : data_(s),
+ size_(detail::const_check(std::is_same<Char, char>::value &&
+ !detail::is_constant_evaluated(true))
+ ? std::strlen(reinterpret_cast<const char*>(s))
+ : std::char_traits<Char>::length(s)) {}
/** Constructs a string reference from a ``std::basic_string`` object. */
template <typename Traits, typename Alloc>
size_(s.size()) {}
/** Returns a pointer to the string data. */
- constexpr auto data() const -> const Char* { return data_; }
+ constexpr auto data() const FMT_NOEXCEPT -> const Char* { return data_; }
/** Returns the string size. */
- constexpr auto size() const -> size_t { return size_; }
+ constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; }
- constexpr auto begin() const -> iterator { return data_; }
- constexpr auto end() const -> iterator { return data_ + size_; }
+ constexpr auto begin() const FMT_NOEXCEPT -> iterator { return data_; }
+ constexpr auto end() const FMT_NOEXCEPT -> iterator { return data_ + size_; }
- constexpr auto operator[](size_t pos) const -> const Char& {
+ constexpr auto operator[](size_t pos) const FMT_NOEXCEPT -> const Char& {
return data_[pos];
}
- FMT_CONSTEXPR void remove_prefix(size_t n) {
+ FMT_CONSTEXPR void remove_prefix(size_t n) FMT_NOEXCEPT {
data_ += n;
size_ -= n;
}
FMT_BEGIN_DETAIL_NAMESPACE
void to_string_view(...);
-using fmt::v8::to_string_view;
+using fmt::to_string_view;
// Specifies whether S is a string type convertible to fmt::basic_string_view.
// It should be a constexpr function but MSVC 2017 fails to compile it in
template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)>
void check_format_string(S);
+FMT_NORETURN FMT_API void throw_format_error(const char* message);
+
struct error_handler {
constexpr error_handler() = default;
constexpr error_handler(const error_handler&) = default;
FMT_BEGIN_DETAIL_NAMESPACE
+template <typename Context, typename T>
+constexpr auto has_const_formatter_impl(T*)
+ -> decltype(typename Context::template formatter_type<T>().format(
+ std::declval<const T&>(), std::declval<Context&>()),
+ true) {
+ return true;
+}
+template <typename Context>
+constexpr auto has_const_formatter_impl(...) -> bool {
+ return false;
+}
+template <typename T, typename Context>
+constexpr auto has_const_formatter() -> bool {
+ return has_const_formatter_impl<Context>(static_cast<T*>(nullptr));
+}
+
// Extracts a reference to the container from back_insert_iterator.
template <typename Container>
inline auto get_container(std::back_insert_iterator<Container> it)
return out;
}
-template <typename Char, FMT_ENABLE_IF(std::is_same<Char, char>::value)>
-FMT_CONSTEXPR auto copy_str(const Char* begin, const Char* end, Char* out)
- -> Char* {
- if (is_constant_evaluated())
- return copy_str<Char, const Char*, Char*>(begin, end, out);
+template <typename Char, typename T, typename U,
+ FMT_ENABLE_IF(
+ std::is_same<remove_const_t<T>, U>::value&& is_char<U>::value)>
+FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* {
+ if (is_constant_evaluated()) return copy_str<Char, T*, U*>(begin, end, out);
auto size = to_unsigned(end - begin);
- memcpy(out, begin, size);
+ memcpy(out, begin, size * sizeof(U));
return out + size;
}
FMT_MSC_WARNING(suppress : 26495)
buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {}
- buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT
- : ptr_(p),
- size_(sz),
- capacity_(cap) {}
+ FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0,
+ size_t cap = 0) FMT_NOEXCEPT : ptr_(p),
+ size_(sz),
+ capacity_(cap) {}
- ~buffer() = default;
+ FMT_CONSTEXPR20 ~buffer() = default;
buffer(buffer&&) = default;
/** Sets the buffer data and capacity. */
- void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
+ FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT {
ptr_ = buf_data;
capacity_ = buf_capacity;
}
/** Increases the buffer capacity to hold at least *capacity* elements. */
- virtual void grow(size_t capacity) = 0;
+ virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0;
public:
using value_type = T;
auto end() const FMT_NOEXCEPT -> const T* { return ptr_ + size_; }
/** Returns the size of this buffer. */
- auto size() const FMT_NOEXCEPT -> size_t { return size_; }
+ constexpr auto size() const FMT_NOEXCEPT -> size_t { return size_; }
/** Returns the capacity of this buffer. */
- auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; }
+ constexpr auto capacity() const FMT_NOEXCEPT -> size_t { return capacity_; }
/** Returns a pointer to the buffer data. */
- auto data() FMT_NOEXCEPT -> T* { return ptr_; }
+ FMT_CONSTEXPR auto data() FMT_NOEXCEPT -> T* { return ptr_; }
/** Returns a pointer to the buffer data. */
- auto data() const FMT_NOEXCEPT -> const T* { return ptr_; }
+ FMT_CONSTEXPR auto data() const FMT_NOEXCEPT -> const T* { return ptr_; }
/** Clears this buffer. */
void clear() { size_ = 0; }
// Tries resizing the buffer to contain *count* elements. If T is a POD type
// the new elements may not be initialized.
- void try_resize(size_t count) {
+ FMT_CONSTEXPR20 void try_resize(size_t count) {
try_reserve(count);
size_ = count <= capacity_ ? count : capacity_;
}
// capacity by a smaller amount than requested but guarantees there is space
// for at least one additional element either by increasing the capacity or by
// flushing the buffer if it is full.
- void try_reserve(size_t new_capacity) {
+ FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) {
if (new_capacity > capacity_) grow(new_capacity);
}
- void push_back(const T& value) {
+ FMT_CONSTEXPR20 void push_back(const T& value) {
try_reserve(size_ + 1);
ptr_[size_++] = value;
}
/** Appends data to the end of the buffer. */
template <typename U> void append(const U* begin, const U* end);
- template <typename I> auto operator[](I index) -> T& { return ptr_[index]; }
- template <typename I> auto operator[](I index) const -> const T& {
+ template <typename I> FMT_CONSTEXPR auto operator[](I index) -> T& {
+ return ptr_[index];
+ }
+ template <typename I>
+ FMT_CONSTEXPR auto operator[](I index) const -> const T& {
return ptr_[index];
}
};
T data_[buffer_size];
protected:
- void grow(size_t) final FMT_OVERRIDE {
+ FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() == buffer_size) flush();
}
auto count() const -> size_t { return Traits::count() + this->size(); }
};
+template <typename T>
+class iterator_buffer<T*, T, fixed_buffer_traits> final
+ : public fixed_buffer_traits,
+ public buffer<T> {
+ private:
+ T* out_;
+ enum { buffer_size = 256 };
+ T data_[buffer_size];
+
+ protected:
+ FMT_CONSTEXPR20 void grow(size_t) override {
+ if (this->size() == this->capacity()) flush();
+ }
+
+ void flush() {
+ size_t n = this->limit(this->size());
+ if (this->data() == out_) {
+ out_ += n;
+ this->set(data_, buffer_size);
+ }
+ this->clear();
+ }
+
+ public:
+ explicit iterator_buffer(T* out, size_t n = buffer_size)
+ : fixed_buffer_traits(n), buffer<T>(out, 0, n), out_(out) {}
+ iterator_buffer(iterator_buffer&& other)
+ : fixed_buffer_traits(other),
+ buffer<T>(std::move(other)),
+ out_(other.out_) {
+ if (this->data() != out_) {
+ this->set(data_, buffer_size);
+ this->clear();
+ }
+ }
+ ~iterator_buffer() { flush(); }
+
+ auto out() -> T* {
+ flush();
+ return out_;
+ }
+ auto count() const -> size_t {
+ return fixed_buffer_traits::count() + this->size();
+ }
+};
+
template <typename T> class iterator_buffer<T*, T> final : public buffer<T> {
protected:
- void grow(size_t) final FMT_OVERRIDE {}
+ FMT_CONSTEXPR20 void grow(size_t) override {}
public:
explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {}
Container& container_;
protected:
- void grow(size_t capacity) final FMT_OVERRIDE {
+ FMT_CONSTEXPR20 void grow(size_t capacity) override {
container_.resize(capacity);
this->set(&container_[0], capacity);
}
size_t count_ = 0;
protected:
- void grow(size_t) final FMT_OVERRIDE {
+ FMT_CONSTEXPR20 void grow(size_t) override {
if (this->size() != buffer_size) return;
count_ += this->size();
this->clear();
return count<is_named_arg<Args>::value...>();
}
+template <typename... Args>
+constexpr auto count_statically_named_args() -> size_t {
+ return count<is_statically_named_arg<Args>::value...>();
+}
+
enum class type {
none_type,
// Integer types should go first,
return t > type::none_type && t <= type::last_numeric_type;
}
+struct unformattable {};
+struct unformattable_char : unformattable {};
+struct unformattable_const : unformattable {};
+struct unformattable_pointer : unformattable {};
+
template <typename Char> struct string_value {
const Char* data;
size_t size;
template <typename Context> struct custom_value {
using parse_context = typename Context::parse_context_type;
- const void* value;
- void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx);
+ void* value;
+ void (*format)(void* arg, parse_context& parse_ctx, Context& ctx);
};
// A formatting argument value.
constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {}
FMT_INLINE value(int128_t val) : int128_value(val) {}
FMT_INLINE value(uint128_t val) : uint128_value(val) {}
- FMT_INLINE value(float val) : float_value(val) {}
- FMT_INLINE value(double val) : double_value(val) {}
+ constexpr FMT_INLINE value(float val) : float_value(val) {}
+ constexpr FMT_INLINE value(double val) : double_value(val) {}
FMT_INLINE value(long double val) : long_double_value(val) {}
constexpr FMT_INLINE value(bool val) : bool_value(val) {}
constexpr FMT_INLINE value(char_type val) : char_value(val) {}
FMT_INLINE value(const named_arg_info<char_type>* args, size_t size)
: named_args{args, size} {}
- template <typename T> FMT_CONSTEXPR FMT_INLINE value(const T& val) {
- custom.value = &val;
+ template <typename T> FMT_CONSTEXPR FMT_INLINE value(T& val) {
+ using value_type = remove_cvref_t<T>;
+ custom.value = const_cast<value_type*>(&val);
// Get the formatter type through the context to allow different contexts
// have different extension points, e.g. `formatter<T>` for `format` and
// `printf_formatter<T>` for `printf`.
custom.format = format_custom_arg<
- T, conditional_t<has_formatter<T, Context>::value,
- typename Context::template formatter_type<T>,
- fallback_formatter<T, char_type>>>;
+ value_type,
+ conditional_t<has_formatter<value_type, Context>::value,
+ typename Context::template formatter_type<value_type>,
+ fallback_formatter<value_type, char_type>>>;
}
+ value(unformattable);
+ value(unformattable_char);
+ value(unformattable_const);
+ value(unformattable_pointer);
private:
// Formats an argument of a custom type, such as a user-defined class.
template <typename T, typename Formatter>
- static void format_custom_arg(const void* arg,
+ static void format_custom_arg(void* arg,
typename Context::parse_context_type& parse_ctx,
Context& ctx) {
- Formatter f;
+ auto f = Formatter();
parse_ctx.advance_to(f.parse(parse_ctx));
- ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx));
+ using qualified_type =
+ conditional_t<has_const_formatter<T, Context>(), const T, T>;
+ ctx.advance_to(f.format(*static_cast<qualified_type*>(arg), ctx));
}
};
using long_type = conditional_t<long_short, int, long long>;
using ulong_type = conditional_t<long_short, unsigned, unsigned long long>;
-struct unformattable {};
-
// Maps formatting arguments to core types.
+// arg_mapper reports errors by returning unformattable instead of using
+// static_assert because it's used in the is_formattable trait.
template <typename Context> struct arg_mapper {
using char_type = typename Context::char_type;
FMT_CONSTEXPR FMT_INLINE auto map(uint128_t val) -> uint128_t { return val; }
FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; }
- template <typename T, FMT_ENABLE_IF(is_char<T>::value)>
+ template <typename T, FMT_ENABLE_IF(std::is_same<T, char>::value ||
+ std::is_same<T, char_type>::value)>
FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type {
- static_assert(
- std::is_same<T, char>::value || std::is_same<T, char_type>::value,
- "mixing character types is disallowed");
return val;
}
+ template <typename T, enable_if_t<(std::is_same<T, wchar_t>::value ||
+#ifdef __cpp_char8_t
+ std::is_same<T, char8_t>::value ||
+#endif
+ std::is_same<T, char16_t>::value ||
+ std::is_same<T, char32_t>::value) &&
+ !std::is_same<T, char_type>::value,
+ int> = 0>
+ FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char {
+ return {};
+ }
FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; }
FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; }
FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* {
return val;
}
- template <typename T, FMT_ENABLE_IF(is_string<T>::value)>
+ template <typename T,
+ FMT_ENABLE_IF(is_string<T>::value && !std::is_pointer<T>::value &&
+ std::is_same<char_type, char_t<T>>::value)>
FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
-> basic_string_view<char_type> {
- static_assert(std::is_same<char_type, char_t<T>>::value,
- "mixing character types is disallowed");
return to_string_view(val);
}
+ template <typename T,
+ FMT_ENABLE_IF(is_string<T>::value && !std::is_pointer<T>::value &&
+ !std::is_same<char_type, char_t<T>>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char {
+ return {};
+ }
template <typename T,
FMT_ENABLE_IF(
std::is_constructible<basic_string_view<char_type>, T>::value &&
-> basic_string_view<char_type> {
return std_string_view<char_type>(val);
}
- FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val) -> const char* {
- static_assert(std::is_same<char_type, char>::value, "invalid string type");
- return reinterpret_cast<const char*>(val);
+
+ using cstring_result = conditional_t<std::is_same<char_type, char>::value,
+ const char*, unformattable_pointer>;
+
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const signed char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val) -> const char* {
- static_assert(std::is_same<char_type, char>::value, "invalid string type");
- return reinterpret_cast<const char*>(val);
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const unsigned char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR FMT_INLINE auto map(signed char* val) -> const char* {
- const auto* const_val = val;
- return map(const_val);
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(signed char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
- FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val) -> const char* {
- const auto* const_val = val;
- return map(const_val);
+ FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(unsigned char* val)
+ -> cstring_result {
+ return map(reinterpret_cast<const char*>(val));
}
FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; }
// We use SFINAE instead of a const T* parameter to avoid conflicting with
// the C array overload.
- template <typename T>
- FMT_CONSTEXPR auto map(T) -> enable_if_t<std::is_pointer<T>::value, int> {
- // Formatting of arbitrary pointers is disallowed. If you want to output
- // a pointer cast it to "void *" or "const void *". In particular, this
- // forbids formatting of "[const] volatile char *" which is printed as bool
- // by iostreams.
- static_assert(!sizeof(T), "formatting of non-void pointers is disallowed");
- return 0;
+ template <
+ typename T,
+ FMT_ENABLE_IF(
+ std::is_member_pointer<T>::value ||
+ std::is_function<typename std::remove_pointer<T>::type>::value ||
+ (std::is_convertible<const T&, const void*>::value &&
+ !std::is_convertible<const T&, const char_type*>::value))>
+ FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer {
+ return {};
}
- template <typename T, std::size_t N>
+ template <typename T, std::size_t N,
+ FMT_ENABLE_IF(!std::is_same<T, wchar_t>::value)>
FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] {
return values;
}
template <typename T,
- FMT_ENABLE_IF(std::is_enum<T>::value &&
- !has_formatter<T, Context>::value &&
- !has_fallback_formatter<T, char_type>::value)>
+ FMT_ENABLE_IF(
+ std::is_enum<T>::value&& std::is_convertible<T, int>::value &&
+ !has_formatter<T, Context>::value &&
+ !has_fallback_formatter<T, char_type>::value)>
FMT_CONSTEXPR FMT_INLINE auto map(const T& val)
-> decltype(std::declval<arg_mapper>().map(
static_cast<typename std::underlying_type<T>::type>(val))) {
return map(static_cast<typename std::underlying_type<T>::type>(val));
}
- template <typename T,
- FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value &&
- (has_formatter<T, Context>::value ||
- has_fallback_formatter<T, char_type>::value))>
- FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> const T& {
+
+ FMT_CONSTEXPR FMT_INLINE auto map(detail::byte val) -> unsigned {
+ return map(static_cast<unsigned char>(val));
+ }
+
+ template <typename T, typename U = remove_cvref_t<T>>
+ struct formattable
+ : bool_constant<has_const_formatter<U, Context>() ||
+ !std::is_const<remove_reference_t<T>>::value ||
+ has_fallback_formatter<U, char_type>::value> {};
+
+#if FMT_MSC_VER != 0 && FMT_MSC_VER < 1910
+ // Workaround a bug in MSVC.
+ template <typename T> FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& {
+ return val;
+ }
+#else
+ template <typename T, FMT_ENABLE_IF(formattable<T>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& {
return val;
}
+ template <typename T, FMT_ENABLE_IF(!formattable<T>::value)>
+ FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const {
+ return {};
+ }
+#endif
+
+ template <typename T, typename U = remove_cvref_t<T>,
+ FMT_ENABLE_IF(!is_string<U>::value && !is_char<U>::value &&
+ !std::is_array<U>::value &&
+ (has_formatter<U, Context>::value ||
+ has_fallback_formatter<U, char_type>::value))>
+ FMT_CONSTEXPR FMT_INLINE auto map(T&& val)
+ -> decltype(this->do_map(std::forward<T>(val))) {
+ return do_map(std::forward<T>(val));
+ }
template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)>
FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg)
public:
using std::back_insert_iterator<detail::buffer<char>>::back_insert_iterator;
- appender(base it) : base(it) {}
+ appender(base it) FMT_NOEXCEPT : base(it) {}
using _Unchecked_type = appender; // Mark iterator as checked.
- auto operator++() -> appender& {
- base::operator++();
- return *this;
- }
+ auto operator++() FMT_NOEXCEPT -> appender& { return *this; }
- auto operator++(int) -> appender {
- auto tmp = *this;
- ++*this;
- return tmp;
- }
+ auto operator++(int) FMT_NOEXCEPT -> appender { return *this; }
};
// A formatting argument. It is a trivially copyable/constructible type to
// another (not recommended).
template <bool IS_PACKED, typename Context, type, typename T,
FMT_ENABLE_IF(IS_PACKED)>
-FMT_CONSTEXPR FMT_INLINE auto make_arg(const T& val) -> value<Context> {
- const auto& arg = arg_mapper<Context>().map(val);
+FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value<Context> {
+ const auto& arg = arg_mapper<Context>().map(std::forward<T>(val));
+
+ constexpr bool formattable_char =
+ !std::is_same<decltype(arg), const unformattable_char&>::value;
+ static_assert(formattable_char, "Mixing character types is disallowed.");
+
+ constexpr bool formattable_const =
+ !std::is_same<decltype(arg), const unformattable_const&>::value;
+ static_assert(formattable_const, "Cannot format a const argument.");
+
+ // Formatting of arbitrary pointers is disallowed. If you want to output
+ // a pointer cast it to "void *" or "const void *". In particular, this
+ // forbids formatting of "[const] volatile char *" which is printed as bool
+ // by iostreams.
+ constexpr bool formattable_pointer =
+ !std::is_same<decltype(arg), const unformattable_pointer&>::value;
+ static_assert(formattable_pointer,
+ "Formatting of non-void pointers is disallowed.");
+
+ constexpr bool formattable =
+ !std::is_same<decltype(arg), const unformattable&>::value;
static_assert(
- !std::is_same<decltype(arg), const unformattable&>::value,
+ formattable,
"Cannot format an argument. To make type T formattable provide a "
"formatter<T> specialization: https://fmt.dev/latest/api.html#udt");
return {arg};
template <typename T, typename Char = char>
using is_formattable = bool_constant<
- !std::is_same<decltype(detail::arg_mapper<buffer_context<Char>>().map(
- std::declval<T>())),
- detail::unformattable>::value &&
+ !std::is_base_of<detail::unformattable,
+ decltype(detail::arg_mapper<buffer_context<Char>>().map(
+ std::declval<T>()))>::value &&
!detail::has_fallback_formatter<T, Char>::value>;
/**
: 0);
public:
- FMT_CONSTEXPR FMT_INLINE format_arg_store(const Args&... args)
+ template <typename... T>
+ FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args)
:
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
basic_format_args<Context>(*this),
#endif
data_{detail::make_arg<
is_packed, Context,
- detail::mapped_type_constant<Args, Context>::value>(args)...} {
+ detail::mapped_type_constant<remove_cvref_t<T>, Context>::value>(
+ std::forward<T>(args))...} {
detail::init_named_args(data_.named_args(), 0, 0, args...);
}
};
\endrst
*/
template <typename Context = format_context, typename... Args>
-constexpr auto make_format_args(const Args&... args)
- -> format_arg_store<Context, Args...> {
- return {args...};
+constexpr auto make_format_args(Args&&... args)
+ -> format_arg_store<Context, remove_cvref_t<Args>...> {
+ return {std::forward<Args>(args)...};
}
/**
FMT_BEGIN_DETAIL_NAMESPACE
-void throw_format_error(const char* message);
-
// Workaround an array initialization issue in gcc 4.8.
template <typename Char> struct fill_t {
private:
};
FMT_END_DETAIL_NAMESPACE
+enum class presentation_type : unsigned char {
+ none,
+ // Integer types should go first,
+ dec, // 'd'
+ oct, // 'o'
+ hex_lower, // 'x'
+ hex_upper, // 'X'
+ bin_lower, // 'b'
+ bin_upper, // 'B'
+ hexfloat_lower, // 'a'
+ hexfloat_upper, // 'A'
+ exp_lower, // 'e'
+ exp_upper, // 'E'
+ fixed_lower, // 'f'
+ fixed_upper, // 'F'
+ general_lower, // 'g'
+ general_upper, // 'G'
+ chr, // 'c'
+ string, // 's'
+ pointer // 'p'
+};
+
// Format specifiers for built-in and string types.
template <typename Char> struct basic_format_specs {
int width;
int precision;
- char type;
+ presentation_type type;
align_t align : 4;
sign_t sign : 3;
bool alt : 1; // Alternate form ('#').
constexpr basic_format_specs()
: width(0),
precision(-1),
- type(0),
+ type(presentation_type::none),
align(align::none),
sign(sign::none),
alt(false),
}
FMT_CONSTEXPR void end_precision() {}
- FMT_CONSTEXPR void on_type(Char type) {
- specs_.type = static_cast<char>(type);
- }
+ FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; }
};
// Format spec handler that saves references to arguments representing dynamic
template <typename Char>
FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int {
if (const_check(sizeof(Char) != 1)) return 1;
- constexpr char lengths[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 3, 3, 4, 0};
+ auto lengths =
+ "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4";
int len = lengths[static_cast<unsigned char>(*begin) >> 3];
// Compute the pointer to the next character early so that the next
return begin;
}
+template <typename Char>
+FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type {
+ switch (to_ascii(type)) {
+ case 'd':
+ return presentation_type::dec;
+ case 'o':
+ return presentation_type::oct;
+ case 'x':
+ return presentation_type::hex_lower;
+ case 'X':
+ return presentation_type::hex_upper;
+ case 'b':
+ return presentation_type::bin_lower;
+ case 'B':
+ return presentation_type::bin_upper;
+ case 'a':
+ return presentation_type::hexfloat_lower;
+ case 'A':
+ return presentation_type::hexfloat_upper;
+ case 'e':
+ return presentation_type::exp_lower;
+ case 'E':
+ return presentation_type::exp_upper;
+ case 'f':
+ return presentation_type::fixed_lower;
+ case 'F':
+ return presentation_type::fixed_upper;
+ case 'g':
+ return presentation_type::general_lower;
+ case 'G':
+ return presentation_type::general_upper;
+ case 'c':
+ return presentation_type::chr;
+ case 's':
+ return presentation_type::string;
+ case 'p':
+ return presentation_type::pointer;
+ default:
+ return presentation_type::none;
+ }
+}
+
// Parses standard format specifiers and sends notifications about parsed
// components to handler.
template <typename Char, typename SpecHandler>
const Char* end,
SpecHandler&& handler)
-> const Char* {
- if (begin + 1 < end && begin[1] == '}' && is_ascii_letter(*begin) &&
+ if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) &&
*begin != 'L') {
- handler.on_type(*begin++);
+ presentation_type type = parse_presentation_type(*begin++);
+ if (type == presentation_type::none)
+ handler.on_error("invalid type specifier");
+ handler.on_type(type);
return begin;
}
}
// Parse type.
- if (begin != end && *begin != '}') handler.on_type(*begin++);
+ if (begin != end && *begin != '}') {
+ presentation_type type = parse_presentation_type(*begin++);
+ if (type == presentation_type::none)
+ handler.on_error("invalid type specifier");
+ handler.on_type(type);
+ }
return begin;
}
template <bool IS_CONSTEXPR, typename Char, typename Handler>
FMT_CONSTEXPR FMT_INLINE void parse_format_string(
basic_string_view<Char> format_str, Handler&& handler) {
- // this is most likely a name-lookup defect in msvc's modules implementation
+ // Workaround a name-lookup bug in MSVC's modules implementation.
using detail::find;
auto begin = format_str.data();
};
template <typename ErrorHandler>
-FMT_CONSTEXPR void check_int_type_spec(char spec, ErrorHandler&& eh) {
- switch (spec) {
- case 0:
- case 'd':
- case 'x':
- case 'X':
- case 'b':
- case 'B':
- case 'o':
- case 'c':
- break;
- default:
+FMT_CONSTEXPR void check_int_type_spec(presentation_type type,
+ ErrorHandler&& eh) {
+ if (type > presentation_type::bin_upper && type != presentation_type::chr)
eh.on_error("invalid type specifier");
- break;
- }
}
// Checks char specs and returns true if the type spec is char (and not int).
template <typename Char, typename ErrorHandler = error_handler>
FMT_CONSTEXPR auto check_char_specs(const basic_format_specs<Char>& specs,
ErrorHandler&& eh = {}) -> bool {
- if (specs.type && specs.type != 'c') {
+ if (specs.type != presentation_type::none &&
+ specs.type != presentation_type::chr) {
check_int_type_spec(specs.type, eh);
return false;
}
bool upper : 1;
bool locale : 1;
bool binary32 : 1;
- bool use_grisu : 1;
+ bool fallback : 1;
bool showpoint : 1;
};
result.showpoint = specs.alt;
result.locale = specs.localized;
switch (specs.type) {
- case 0:
+ case presentation_type::none:
result.format = float_format::general;
break;
- case 'G':
+ case presentation_type::general_upper:
result.upper = true;
FMT_FALLTHROUGH;
- case 'g':
+ case presentation_type::general_lower:
result.format = float_format::general;
break;
- case 'E':
+ case presentation_type::exp_upper:
result.upper = true;
FMT_FALLTHROUGH;
- case 'e':
+ case presentation_type::exp_lower:
result.format = float_format::exp;
result.showpoint |= specs.precision != 0;
break;
- case 'F':
+ case presentation_type::fixed_upper:
result.upper = true;
FMT_FALLTHROUGH;
- case 'f':
+ case presentation_type::fixed_lower:
result.format = float_format::fixed;
result.showpoint |= specs.precision != 0;
break;
- case 'A':
+ case presentation_type::hexfloat_upper:
result.upper = true;
FMT_FALLTHROUGH;
- case 'a':
+ case presentation_type::hexfloat_lower:
result.format = float_format::hex;
break;
default:
return result;
}
-template <typename Char, typename ErrorHandler = error_handler>
-FMT_CONSTEXPR auto check_cstring_type_spec(Char spec, ErrorHandler&& eh = {})
- -> bool {
- if (spec == 0 || spec == 's') return true;
- if (spec != 'p') eh.on_error("invalid type specifier");
+template <typename ErrorHandler = error_handler>
+FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type,
+ ErrorHandler&& eh = {}) -> bool {
+ if (type == presentation_type::none || type == presentation_type::string)
+ return true;
+ if (type != presentation_type::pointer) eh.on_error("invalid type specifier");
return false;
}
-template <typename Char, typename ErrorHandler = error_handler>
-FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler&& eh = {}) {
- if (spec != 0 && spec != 's') eh.on_error("invalid type specifier");
+template <typename ErrorHandler = error_handler>
+FMT_CONSTEXPR void check_string_type_spec(presentation_type type,
+ ErrorHandler&& eh = {}) {
+ if (type != presentation_type::none && type != presentation_type::string)
+ eh.on_error("invalid type specifier");
}
-template <typename Char, typename ErrorHandler>
-FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler&& eh) {
- if (spec != 0 && spec != 'p') eh.on_error("invalid type specifier");
+template <typename ErrorHandler>
+FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type,
+ ErrorHandler&& eh) {
+ if (type != presentation_type::none && type != presentation_type::pointer)
+ eh.on_error("invalid type specifier");
}
// A parse_format_specs handler that checks if specifiers are consistent with
if constexpr (detail::is_statically_named_arg<T>()) {
if (name == T::name) return N;
}
- if constexpr (sizeof...(Args) > 0) {
+ if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<N + 1, Args...>(name);
- } else {
- (void)name; // Workaround an MSVC bug about "unused" parameter.
- return invalid_arg_index;
- }
+ (void)name; // Workaround an MSVC bug about "unused" parameter.
+ return invalid_arg_index;
}
#endif
template <typename... Args, typename Char>
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
- if constexpr (sizeof...(Args) > 0) {
+ if constexpr (sizeof...(Args) > 0)
return get_arg_index_by_name<0, Args...>(name);
- } else {
- (void)name;
- return invalid_arg_index;
- }
-#else
+#endif
(void)name;
return invalid_arg_index;
-#endif
}
template <typename Char, typename ErrorHandler, typename... Args>
FMT_ASSERT(false, "invalid argument type");
break;
case detail::type::bool_type:
- if (!specs_.type || specs_.type == 's') break;
+ if (specs_.type == presentation_type::none ||
+ specs_.type == presentation_type::string) {
+ break;
+ }
FMT_FALLTHROUGH;
case detail::type::int_type:
case detail::type::uint_type:
template <typename Char> struct basic_runtime { basic_string_view<Char> str; };
+/** A compile-time format string. */
template <typename Char, typename... Args> class basic_format_string {
private:
basic_string_view<Char> str_;
template <typename S,
FMT_ENABLE_IF(
std::is_convertible<const S&, basic_string_view<Char>>::value)>
- FMT_CONSTEVAL basic_format_string(const S& s) : str_(s) {
+ FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) {
static_assert(
detail::count<
(std::is_base_of<detail::view, remove_reference_t<Args>>::value &&
std::is_reference<Args>::value)...>() == 0,
"passing views as lvalues is disallowed");
#ifdef FMT_HAS_CONSTEVAL
- if constexpr (detail::count_named_args<Args...>() == 0) {
+ if constexpr (detail::count_named_args<Args...>() ==
+ detail::count_statically_named_args<Args...>()) {
using checker = detail::format_string_checker<Char, detail::error_handler,
remove_cvref_t<Args>...>;
detail::parse_format_string<true>(str_, checker(s, {}));
#else
template <typename... Args>
using format_string = basic_format_string<char, type_identity_t<Args>...>;
-// Creates a runtime format string.
+/**
+ \rst
+ Creates a runtime format string.
+
+ **Example**::
+
+ // Check format string at runtime instead of compile-time.
+ fmt::print(fmt::runtime("{:d}"), "I am not a number");
+ \endrst
+ */
template <typename S> auto runtime(const S& s) -> basic_runtime<char_t<S>> {
return {{s}};
}
**Example**::
#include <fmt/core.h>
- std::string message = fmt::format("The answer is {}", 42);
+ std::string message = fmt::format("The answer is {}.", 42);
\endrst
*/
template <typename... T>
-FMT_INLINE auto format(format_string<T...> fmt, T&&... args) -> std::string {
+FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)
+ -> std::string {
return vformat(fmt, fmt::make_format_args(args...));
}
auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt {
using detail::get_buffer;
auto&& buf = get_buffer<char>(out);
- detail::vformat_to(buf, string_view(fmt), args, {});
+ detail::vformat_to(buf, fmt, args, {});
return detail::get_iterator(buf);
}
\rst
Formats ``args`` according to specifications in ``fmt``, writes the result to
the output iterator ``out`` and returns the iterator past the end of the output
- range.
+ range. `format_to` does not append a terminating null character.
**Example**::
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args)
-> format_to_n_result<OutputIt> {
- using buffer =
- detail::iterator_buffer<OutputIt, char, detail::fixed_buffer_traits>;
- auto buf = buffer(out, n);
+ using traits = detail::fixed_buffer_traits;
+ auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
detail::vformat_to(buf, fmt, args, {});
return {buf.out(), buf.count()};
}
Formats ``args`` according to specifications in ``fmt``, writes up to ``n``
characters of the result to the output iterator ``out`` and returns the total
(not truncated) output size and the iterator past the end of the output range.
+ `format_to_n` does not append a terminating null character.
\endrst
*/
template <typename OutputIt, typename... T,
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt,
- const T&... args) -> format_to_n_result<OutputIt> {
+ T&&... args) -> format_to_n_result<OutputIt> {
return vformat_to_n(out, n, fmt, fmt::make_format_args(args...));
}
/** Returns the number of chars in the output of ``format(fmt, args...)``. */
template <typename... T>
-FMT_INLINE auto formatted_size(format_string<T...> fmt, T&&... args) -> size_t {
+FMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt,
+ T&&... args) -> size_t {
auto buf = detail::counting_buffer<>();
detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {});
return buf.count();
std::terminate();
}
+FMT_FUNC void throw_format_error(const char* message) {
+ FMT_THROW(format_error(message));
+}
+
#ifndef _MSC_VER
# define FMT_SNPRINTF snprintf
#else // _MSC_VER
return i >= 0 ? i * char_digits + count_digits<4, unsigned>(n.value[i]) : 1;
}
+// log10(2) = 0x0.4d104d427de7fbcc...
+static constexpr uint64_t log10_2_significand = 0x4d104d427de7fbcc;
+
+template <typename T = void> struct basic_impl_data {
+ // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
+ // These are generated by support/compute-powers.py.
+ static constexpr uint64_t pow10_significands[87] = {
+ 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
+ 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
+ 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
+ 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
+ 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
+ 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
+ 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
+ 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
+ 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
+ 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
+ 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
+ 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
+ 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
+ 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
+ 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
+ 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
+ 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
+ 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
+ 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
+ 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
+ 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
+ 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
+ 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
+ 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
+ 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
+ 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
+ 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
+ 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
+ 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
+ };
+
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wnarrowing"
+#endif
+ // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
+ // to significands above.
+ static constexpr int16_t pow10_exponents[87] = {
+ -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
+ -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
+ -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
+ -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
+ -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
+ 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
+ 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
+ 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
+#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
+# pragma GCC diagnostic pop
+#endif
+
+ static constexpr uint64_t power_of_10_64[20] = {
+ 1, FMT_POWERS_OF_10(1ULL), FMT_POWERS_OF_10(1000000000ULL),
+ 10000000000000000000ULL};
+};
+
+// This is a struct rather than an alias to avoid shadowing warnings in gcc.
+struct impl_data : basic_impl_data<> {};
+
#if __cplusplus < 201703L
-template <typename T> constexpr const char basic_data<T>::digits[][2];
-template <typename T> constexpr const char basic_data<T>::hex_digits[];
-template <typename T> constexpr const char basic_data<T>::signs[];
-template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
-template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
template <typename T>
-constexpr const char basic_data<T>::right_padding_shifts[];
+constexpr uint64_t basic_impl_data<T>::pow10_significands[];
+template <typename T> constexpr int16_t basic_impl_data<T>::pow10_exponents[];
+template <typename T> constexpr uint64_t basic_impl_data<T>::power_of_10_64[];
#endif
template <typename T> struct bits {
static_cast<int>(sizeof(T) * std::numeric_limits<unsigned char>::digits);
};
-class fp;
-template <int SHIFT = 0> fp normalize(fp value);
-
-// Lower (upper) boundary is a value half way between a floating-point value
-// and its predecessor (successor). Boundaries have the same exponent as the
-// value so only significands are stored.
-struct boundaries {
- uint64_t lower;
- uint64_t upper;
-};
-
-// A handmade floating-point number f * pow(2, e).
-class fp {
- private:
- using significand_type = uint64_t;
-
- template <typename Float>
- using is_supported_float = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
- sizeof(Float) == sizeof(uint32_t)>;
+// Returns the number of significand bits in Float excluding the implicit bit.
+template <typename Float> constexpr int num_significand_bits() {
+ // Subtract 1 to account for an implicit most significant bit in the
+ // normalized form.
+ return std::numeric_limits<Float>::digits - 1;
+}
- public:
- significand_type f;
+// A floating-point number f * pow(2, e).
+struct fp {
+ uint64_t f;
int e;
- // All sizes are in bits.
- // Subtract 1 to account for an implicit most significant bit in the
- // normalized form.
- static FMT_CONSTEXPR_DECL const int double_significand_size =
- std::numeric_limits<double>::digits - 1;
- static FMT_CONSTEXPR_DECL const uint64_t implicit_bit =
- 1ULL << double_significand_size;
- static FMT_CONSTEXPR_DECL const int significand_size =
- bits<significand_type>::value;
+ static constexpr const int num_significand_bits = bits<decltype(f)>::value;
- fp() : f(0), e(0) {}
- fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
+ constexpr fp() : f(0), e(0) {}
+ constexpr fp(uint64_t f_val, int e_val) : f(f_val), e(e_val) {}
- // Constructs fp from an IEEE754 double. It is a template to prevent compile
- // errors on platforms where double is not IEEE754.
- template <typename Double> explicit fp(Double d) { assign(d); }
+ // Constructs fp from an IEEE754 floating-point number. It is a template to
+ // prevent compile errors on systems where n is not IEEE754.
+ template <typename Float> explicit FMT_CONSTEXPR fp(Float n) { assign(n); }
+
+ template <typename Float>
+ using is_supported = bool_constant<sizeof(Float) == sizeof(uint64_t) ||
+ sizeof(Float) == sizeof(uint32_t)>;
// Assigns d to this and return true iff predecessor is closer than successor.
- template <typename Float, FMT_ENABLE_IF(is_supported_float<Float>::value)>
- bool assign(Float d) {
+ template <typename Float, FMT_ENABLE_IF(is_supported<Float>::value)>
+ FMT_CONSTEXPR bool assign(Float n) {
// Assume float is in the format [sign][exponent][significand].
- using limits = std::numeric_limits<Float>;
- const int float_significand_size = limits::digits - 1;
- const int exponent_size =
- bits<Float>::value - float_significand_size - 1; // -1 for sign
- const uint64_t float_implicit_bit = 1ULL << float_significand_size;
- const uint64_t significand_mask = float_implicit_bit - 1;
- const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
- const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1;
+ const int num_float_significand_bits =
+ detail::num_significand_bits<Float>();
+ const uint64_t implicit_bit = 1ULL << num_float_significand_bits;
+ const uint64_t significand_mask = implicit_bit - 1;
constexpr bool is_double = sizeof(Float) == sizeof(uint64_t);
- auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(d);
+ auto u = bit_cast<conditional_t<is_double, uint64_t, uint32_t>>(n);
f = u & significand_mask;
+ const uint64_t exponent_mask = (~0ULL >> 1) & ~significand_mask;
int biased_e =
- static_cast<int>((u & exponent_mask) >> float_significand_size);
- // Predecessor is closer if d is a normalized power of 2 (f == 0) other than
- // the smallest normalized number (biased_e > 1).
+ static_cast<int>((u & exponent_mask) >> num_float_significand_bits);
+ // The predecessor is closer if n is a normalized power of 2 (f == 0) other
+ // than the smallest normalized number (biased_e > 1).
bool is_predecessor_closer = f == 0 && biased_e > 1;
if (biased_e != 0)
- f += float_implicit_bit;
+ f += implicit_bit;
else
biased_e = 1; // Subnormals use biased exponent 1 (min exponent).
- e = biased_e - exponent_bias - float_significand_size;
+ const int exponent_bias = std::numeric_limits<Float>::max_exponent - 1;
+ e = biased_e - exponent_bias - num_float_significand_bits;
return is_predecessor_closer;
}
- template <typename Float, FMT_ENABLE_IF(!is_supported_float<Float>::value)>
+ template <typename Float, FMT_ENABLE_IF(!is_supported<Float>::value)>
bool assign(Float) {
- *this = fp();
+ FMT_ASSERT(false, "");
return false;
}
};
// Normalizes the value converted from double and multiplied by (1 << SHIFT).
-template <int SHIFT> fp normalize(fp value) {
+template <int SHIFT = 0> FMT_CONSTEXPR fp normalize(fp value) {
// Handle subnormals.
- const auto shifted_implicit_bit = fp::implicit_bit << SHIFT;
+ const uint64_t implicit_bit = 1ULL << num_significand_bits<double>();
+ const auto shifted_implicit_bit = implicit_bit << SHIFT;
while ((value.f & shifted_implicit_bit) == 0) {
value.f <<= 1;
--value.e;
}
// Subtract 1 to account for hidden bit.
const auto offset =
- fp::significand_size - fp::double_significand_size - SHIFT - 1;
+ fp::num_significand_bits - num_significand_bits<double>() - SHIFT - 1;
value.f <<= offset;
value.e -= offset;
return value;
inline bool operator==(fp x, fp y) { return x.f == y.f && x.e == y.e; }
// Computes lhs * rhs / pow(2, 64) rounded to nearest with half-up tie breaking.
-inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
+FMT_CONSTEXPR inline uint64_t multiply(uint64_t lhs, uint64_t rhs) {
#if FMT_USE_INT128
auto product = static_cast<__uint128_t>(lhs) * rhs;
auto f = static_cast<uint64_t>(product >> 64);
#endif
}
-inline fp operator*(fp x, fp y) { return {multiply(x.f, y.f), x.e + y.e + 64}; }
+FMT_CONSTEXPR inline fp operator*(fp x, fp y) {
+ return {multiply(x.f, y.f), x.e + y.e + 64};
+}
// Returns a cached power of 10 `c_k = c_k.f * pow(2, c_k.e)` such that its
// (binary) exponent satisfies `min_exponent <= c_k.e <= min_exponent + 28`.
-inline fp get_cached_power(int min_exponent, int& pow10_exponent) {
- // Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340.
- // These are generated by support/compute-powers.py.
- static constexpr const uint64_t pow10_significands[] = {
- 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76,
- 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df,
- 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c,
- 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5,
- 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57,
- 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7,
- 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e,
- 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996,
- 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126,
- 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053,
- 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f,
- 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b,
- 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06,
- 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb,
- 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000,
- 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984,
- 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068,
- 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8,
- 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758,
- 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85,
- 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d,
- 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25,
- 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2,
- 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a,
- 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410,
- 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129,
- 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85,
- 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841,
- 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b,
- };
-
- // Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding
- // to significands above.
- static constexpr const int16_t pow10_exponents[] = {
- -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954,
- -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661,
- -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369,
- -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77,
- -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216,
- 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508,
- 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800,
- 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066};
-
+FMT_CONSTEXPR inline fp get_cached_power(int min_exponent,
+ int& pow10_exponent) {
const int shift = 32;
- const auto significand = static_cast<int64_t>(data::log10_2_significand);
+ const auto significand = static_cast<int64_t>(log10_2_significand);
int index = static_cast<int>(
- ((min_exponent + fp::significand_size - 1) * (significand >> shift) +
+ ((min_exponent + fp::num_significand_bits - 1) * (significand >> shift) +
((int64_t(1) << shift) - 1)) // ceil
>> 32 // arithmetic shift
);
const int dec_exp_step = 8;
index = (index - first_dec_exp - 1) / dec_exp_step + 1;
pow10_exponent = first_dec_exp + index * dec_exp_step;
- return {pow10_significands[index], pow10_exponents[index]};
+ return {impl_data::pow10_significands[index],
+ impl_data::pow10_exponents[index]};
}
// A simple accumulator to hold the sums of terms in bigint::square if uint128_t
uint64_t lower;
uint64_t upper;
- accumulator() : lower(0), upper(0) {}
- explicit operator uint32_t() const { return static_cast<uint32_t>(lower); }
+ constexpr accumulator() : lower(0), upper(0) {}
+ constexpr explicit operator uint32_t() const {
+ return static_cast<uint32_t>(lower);
+ }
- void operator+=(uint64_t n) {
+ FMT_CONSTEXPR void operator+=(uint64_t n) {
lower += n;
if (lower < n) ++upper;
}
- void operator>>=(int shift) {
+ FMT_CONSTEXPR void operator>>=(int shift) {
FMT_ASSERT(shift == 32, "");
(void)shift;
lower = (upper << 32) | (lower >> 32);
basic_memory_buffer<bigit, bigits_capacity> bigits_;
int exp_;
- bigit operator[](int index) const { return bigits_[to_unsigned(index)]; }
- bigit& operator[](int index) { return bigits_[to_unsigned(index)]; }
+ FMT_CONSTEXPR20 bigit operator[](int index) const {
+ return bigits_[to_unsigned(index)];
+ }
+ FMT_CONSTEXPR20 bigit& operator[](int index) {
+ return bigits_[to_unsigned(index)];
+ }
static FMT_CONSTEXPR_DECL const int bigit_bits = bits<bigit>::value;
friend struct formatter<bigint>;
- void subtract_bigits(int index, bigit other, bigit& borrow) {
+ FMT_CONSTEXPR20 void subtract_bigits(int index, bigit other, bigit& borrow) {
auto result = static_cast<double_bigit>((*this)[index]) - other - borrow;
(*this)[index] = static_cast<bigit>(result);
borrow = static_cast<bigit>(result >> (bigit_bits * 2 - 1));
}
- void remove_leading_zeros() {
+ FMT_CONSTEXPR20 void remove_leading_zeros() {
int num_bigits = static_cast<int>(bigits_.size()) - 1;
while (num_bigits > 0 && (*this)[num_bigits] == 0) --num_bigits;
bigits_.resize(to_unsigned(num_bigits + 1));
}
// Computes *this -= other assuming aligned bigints and *this >= other.
- void subtract_aligned(const bigint& other) {
+ FMT_CONSTEXPR20 void subtract_aligned(const bigint& other) {
FMT_ASSERT(other.exp_ >= exp_, "unaligned bigints");
FMT_ASSERT(compare(*this, other) >= 0, "");
bigit borrow = 0;
remove_leading_zeros();
}
- void multiply(uint32_t value) {
+ FMT_CONSTEXPR20 void multiply(uint32_t value) {
const double_bigit wide_value = value;
bigit carry = 0;
for (size_t i = 0, n = bigits_.size(); i < n; ++i) {
if (carry != 0) bigits_.push_back(carry);
}
- void multiply(uint64_t value) {
+ FMT_CONSTEXPR20 void multiply(uint64_t value) {
const bigit mask = ~bigit(0);
const double_bigit lower = value & mask;
const double_bigit upper = value >> bigit_bits;
}
public:
- bigint() : exp_(0) {}
+ FMT_CONSTEXPR20 bigint() : exp_(0) {}
explicit bigint(uint64_t n) { assign(n); }
- ~bigint() { FMT_ASSERT(bigits_.capacity() <= bigits_capacity, ""); }
+ FMT_CONSTEXPR20 ~bigint() {
+ FMT_ASSERT(bigits_.capacity() <= bigits_capacity, "");
+ }
bigint(const bigint&) = delete;
void operator=(const bigint&) = delete;
- void assign(const bigint& other) {
+ FMT_CONSTEXPR20 void assign(const bigint& other) {
auto size = other.bigits_.size();
bigits_.resize(size);
auto data = other.bigits_.data();
exp_ = other.exp_;
}
- void assign(uint64_t n) {
+ FMT_CONSTEXPR20 void assign(uint64_t n) {
size_t num_bigits = 0;
do {
bigits_[num_bigits++] = n & ~bigit(0);
exp_ = 0;
}
- int num_bigits() const { return static_cast<int>(bigits_.size()) + exp_; }
+ FMT_CONSTEXPR20 int num_bigits() const {
+ return static_cast<int>(bigits_.size()) + exp_;
+ }
- FMT_NOINLINE bigint& operator<<=(int shift) {
+ FMT_NOINLINE FMT_CONSTEXPR20 bigint& operator<<=(int shift) {
FMT_ASSERT(shift >= 0, "");
exp_ += shift / bigit_bits;
shift %= bigit_bits;
return *this;
}
- template <typename Int> bigint& operator*=(Int value) {
+ template <typename Int> FMT_CONSTEXPR20 bigint& operator*=(Int value) {
FMT_ASSERT(value > 0, "");
multiply(uint32_or_64_or_128_t<Int>(value));
return *this;
}
- friend int compare(const bigint& lhs, const bigint& rhs) {
+ friend FMT_CONSTEXPR20 int compare(const bigint& lhs, const bigint& rhs) {
int num_lhs_bigits = lhs.num_bigits(), num_rhs_bigits = rhs.num_bigits();
if (num_lhs_bigits != num_rhs_bigits)
return num_lhs_bigits > num_rhs_bigits ? 1 : -1;
}
// Returns compare(lhs1 + lhs2, rhs).
- friend int add_compare(const bigint& lhs1, const bigint& lhs2,
- const bigint& rhs) {
+ friend FMT_CONSTEXPR20 int add_compare(const bigint& lhs1, const bigint& lhs2,
+ const bigint& rhs) {
int max_lhs_bigits = (std::max)(lhs1.num_bigits(), lhs2.num_bigits());
int num_rhs_bigits = rhs.num_bigits();
if (max_lhs_bigits + 1 < num_rhs_bigits) return -1;
}
// Assigns pow(10, exp) to this bigint.
- void assign_pow10(int exp) {
+ FMT_CONSTEXPR20 void assign_pow10(int exp) {
FMT_ASSERT(exp >= 0, "");
if (exp == 0) return assign(1);
// Find the top bit.
*this <<= exp; // Multiply by pow(2, exp) by shifting.
}
- void square() {
+ FMT_CONSTEXPR20 void square() {
int num_bigits = static_cast<int>(bigits_.size());
int num_result_bigits = 2 * num_bigits;
basic_memory_buffer<bigit, bigits_capacity> n(std::move(bigits_));
// If this bigint has a bigger exponent than other, adds trailing zero to make
// exponents equal. This simplifies some operations such as subtraction.
- void align(const bigint& other) {
+ FMT_CONSTEXPR20 void align(const bigint& other) {
int exp_difference = exp_ - other.exp_;
if (exp_difference <= 0) return;
int num_bigits = static_cast<int>(bigits_.size());
// Divides this bignum by divisor, assigning the remainder to this and
// returning the quotient.
- int divmod_assign(const bigint& divisor) {
+ FMT_CONSTEXPR20 int divmod_assign(const bigint& divisor) {
FMT_ASSERT(this != &divisor, "");
if (compare(*this, divisor) < 0) return 0;
FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, "");
// some number v and the error, returns whether v should be rounded up, down, or
// whether the rounding direction can't be determined due to error.
// error should be less than divisor / 2.
-inline round_direction get_round_direction(uint64_t divisor, uint64_t remainder,
- uint64_t error) {
+FMT_CONSTEXPR inline round_direction get_round_direction(uint64_t divisor,
+ uint64_t remainder,
+ uint64_t error) {
FMT_ASSERT(remainder < divisor, ""); // divisor - remainder won't overflow.
FMT_ASSERT(error < divisor, ""); // divisor - error won't overflow.
FMT_ASSERT(error < divisor - error, ""); // error * 2 won't overflow.
};
}
-inline uint64_t power_of_10_64(int exp) {
- static constexpr const uint64_t data[] = {1, FMT_POWERS_OF_10(1),
- FMT_POWERS_OF_10(1000000000ULL),
- 10000000000000000000ULL};
- return data[exp];
-}
+struct gen_digits_handler {
+ char* buf;
+ int size;
+ int precision;
+ int exp10;
+ bool fixed;
+
+ FMT_CONSTEXPR digits::result on_digit(char digit, uint64_t divisor,
+ uint64_t remainder, uint64_t error,
+ bool integral) {
+ FMT_ASSERT(remainder < divisor, "");
+ buf[size++] = digit;
+ if (!integral && error >= remainder) return digits::error;
+ if (size < precision) return digits::more;
+ if (!integral) {
+ // Check if error * 2 < divisor with overflow prevention.
+ // The check is not needed for the integral part because error = 1
+ // and divisor > (1 << 32) there.
+ if (error >= divisor || error >= divisor - error) return digits::error;
+ } else {
+ FMT_ASSERT(error == 1 && divisor > 2, "");
+ }
+ auto dir = get_round_direction(divisor, remainder, error);
+ if (dir != round_direction::up)
+ return dir == round_direction::down ? digits::done : digits::error;
+ ++buf[size - 1];
+ for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
+ buf[i] = '0';
+ ++buf[i - 1];
+ }
+ if (buf[0] > '9') {
+ buf[0] = '1';
+ if (fixed)
+ buf[size++] = '0';
+ else
+ ++exp10;
+ }
+ return digits::done;
+ }
+};
// Generates output using the Grisu digit-gen algorithm.
// error: the size of the region (lower, upper) outside of which numbers
// definitely do not round to value (Delta in Grisu3).
-template <typename Handler>
-FMT_INLINE digits::result grisu_gen_digits(fp value, uint64_t error, int& exp,
- Handler& handler) {
+FMT_INLINE FMT_CONSTEXPR20 digits::result grisu_gen_digits(
+ fp value, uint64_t error, int& exp, gen_digits_handler& handler) {
const fp one(1ULL << -value.e, value.e);
// The integral part of scaled value (p1 in Grisu) = value / one. It cannot be
// zero because it contains a product of two 64-bit numbers with MSB set (due
// The fractional part of scaled value (p2 in Grisu) c = value % one.
uint64_t fractional = value.f & (one.f - 1);
exp = count_digits(integral); // kappa in Grisu.
- // Divide by 10 to prevent overflow.
- auto result = handler.on_start(power_of_10_64(exp - 1) << -one.e,
- value.f / 10, error * 10, exp);
- if (result != digits::more) return result;
+ // Non-fixed formats require at least one digit and no precision adjustment.
+ if (handler.fixed) {
+ // Adjust fixed precision by exponent because it is relative to decimal
+ // point.
+ int precision_offset = exp + handler.exp10;
+ if (precision_offset > 0 &&
+ handler.precision > max_value<int>() - precision_offset) {
+ FMT_THROW(format_error("number is too big"));
+ }
+ handler.precision += precision_offset;
+ // Check if precision is satisfied just by leading zeros, e.g.
+ // format("{:.2f}", 0.001) gives "0.00" without generating any digits.
+ if (handler.precision <= 0) {
+ if (handler.precision < 0) return digits::done;
+ // Divide by 10 to prevent overflow.
+ uint64_t divisor = impl_data::power_of_10_64[exp - 1] << -one.e;
+ auto dir = get_round_direction(divisor, value.f / 10, error * 10);
+ if (dir == round_direction::unknown) return digits::error;
+ handler.buf[handler.size++] = dir == round_direction::up ? '1' : '0';
+ return digits::done;
+ }
+ }
// Generate digits for the integral part. This can produce up to 10 digits.
do {
uint32_t digit = 0;
}
--exp;
auto remainder = (static_cast<uint64_t>(integral) << -one.e) + fractional;
- result = handler.on_digit(static_cast<char>('0' + digit),
- power_of_10_64(exp) << -one.e, remainder, error,
- exp, true);
+ auto result = handler.on_digit(static_cast<char>('0' + digit),
+ impl_data::power_of_10_64[exp] << -one.e,
+ remainder, error, true);
if (result != digits::more) return result;
} while (exp > 0);
// Generate digits for the fractional part.
char digit = static_cast<char>('0' + (fractional >> -one.e));
fractional &= one.f - 1;
--exp;
- result = handler.on_digit(digit, one.f, fractional, error, exp, false);
+ auto result = handler.on_digit(digit, one.f, fractional, error, false);
if (result != digits::more) return result;
}
}
-// The fixed precision digit handler.
-struct fixed_handler {
- char* buf;
- int size;
- int precision;
- int exp10;
- bool fixed;
-
- digits::result on_start(uint64_t divisor, uint64_t remainder, uint64_t error,
- int& exp) {
- // Non-fixed formats require at least one digit and no precision adjustment.
- if (!fixed) return digits::more;
- // Adjust fixed precision by exponent because it is relative to decimal
- // point.
- precision += exp + exp10;
- // Check if precision is satisfied just by leading zeros, e.g.
- // format("{:.2f}", 0.001) gives "0.00" without generating any digits.
- if (precision > 0) return digits::more;
- if (precision < 0) return digits::done;
- auto dir = get_round_direction(divisor, remainder, error);
- if (dir == round_direction::unknown) return digits::error;
- buf[size++] = dir == round_direction::up ? '1' : '0';
- return digits::done;
- }
-
- digits::result on_digit(char digit, uint64_t divisor, uint64_t remainder,
- uint64_t error, int, bool integral) {
- FMT_ASSERT(remainder < divisor, "");
- buf[size++] = digit;
- if (!integral && error >= remainder) return digits::error;
- if (size < precision) return digits::more;
- if (!integral) {
- // Check if error * 2 < divisor with overflow prevention.
- // The check is not needed for the integral part because error = 1
- // and divisor > (1 << 32) there.
- if (error >= divisor || error >= divisor - error) return digits::error;
- } else {
- FMT_ASSERT(error == 1 && divisor > 2, "");
- }
- auto dir = get_round_direction(divisor, remainder, error);
- if (dir != round_direction::up)
- return dir == round_direction::down ? digits::done : digits::error;
- ++buf[size - 1];
- for (int i = size - 1; i > 0 && buf[i] > '9'; --i) {
- buf[i] = '0';
- ++buf[i - 1];
- }
- if (buf[0] > '9') {
- buf[0] = '1';
- if (fixed)
- buf[size++] = '0';
- else
- ++exp10;
- }
- return digits::done;
- }
-};
-
// A 128-bit integer type used internally,
struct uint128_wrapper {
uint128_wrapper() = default;
inline int floor_log10_pow2(int e) FMT_NOEXCEPT {
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const int shift = 22;
- return (e * static_cast<int>(data::log10_2_significand >> (64 - shift))) >>
- shift;
+ return (e * static_cast<int>(log10_2_significand >> (64 - shift))) >> shift;
}
// Various fast log computations.
FMT_ASSERT(e <= 1700 && e >= -1700, "too large exponent");
const uint64_t log10_4_over_3_fractional_digits = 0x1ffbfc2bbc780375;
const int shift_amount = 22;
- return (e * static_cast<int>(data::log10_2_significand >>
- (64 - shift_amount)) -
+ return (e * static_cast<int>(log10_2_significand >> (64 - shift_amount)) -
static_cast<int>(log10_4_over_3_fractional_digits >>
(64 - shift_amount))) >>
shift_amount;
static uint64_t get_cached_power(int k) FMT_NOEXCEPT {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range");
- constexpr const uint64_t pow10_significands[] = {
+ static constexpr const uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
}
} // namespace dragonbox
-// Formats value using a variation of the Fixed-Precision Positive
-// Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
+// Formats a floating-point number using a variation of the Fixed-Precision
+// Positive Floating-Point Printout ((FPP)^2) algorithm by Steele & White:
// https://fmt.dev/papers/p372-steele.pdf.
-template <typename Double>
-void fallback_format(Double d, int num_digits, bool binary32, buffer<char>& buf,
- int& exp10) {
+FMT_CONSTEXPR20 inline void format_dragon(fp value, bool is_predecessor_closer,
+ int num_digits, buffer<char>& buf,
+ int& exp10) {
bigint numerator; // 2 * R in (FPP)^2.
bigint denominator; // 2 * S in (FPP)^2.
// lower and upper are differences between value and corresponding boundaries.
bigint lower; // (M^- in (FPP)^2).
bigint upper_store; // upper's value if different from lower.
bigint* upper = nullptr; // (M^+ in (FPP)^2).
- fp value;
// Shift numerator and denominator by an extra bit or two (if lower boundary
// is closer) to make lower and upper integers. This eliminates multiplication
// by 2 during later computations.
- const bool is_predecessor_closer =
- binary32 ? value.assign(static_cast<float>(d)) : value.assign(d);
int shift = is_predecessor_closer ? 2 : 1;
uint64_t significand = value.f << shift;
if (value.e >= 0) {
// Generate the given number of digits.
exp10 -= num_digits - 1;
if (num_digits == 0) {
- buf.try_resize(1);
denominator *= 10;
- buf[0] = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
+ auto digit = add_compare(numerator, numerator, denominator) > 0 ? '1' : '0';
+ buf.push_back(digit);
return;
}
buf.try_resize(to_unsigned(num_digits));
buf[num_digits - 1] = static_cast<char>('0' + digit);
}
-template <typename T>
-int format_float(T value, int precision, float_specs specs, buffer<char>& buf) {
- static_assert(!std::is_same<T, float>::value, "");
+template <typename Float>
+FMT_HEADER_ONLY_CONSTEXPR20 int format_float(Float value, int precision,
+ float_specs specs,
+ buffer<char>& buf) {
+ // float is passed as double to reduce the number of instantiations.
+ static_assert(!std::is_same<Float, float>::value, "");
FMT_ASSERT(value >= 0, "value is negative");
const bool fixed = specs.format == float_format::fixed;
return 0;
}
buf.try_resize(to_unsigned(precision));
- std::uninitialized_fill_n(buf.data(), precision, '0');
+ fill_n(buf.data(), precision, '0');
return -precision;
}
- if (!specs.use_grisu) return snprintf_float(value, precision, specs, buf);
+ if (specs.fallback) return snprintf_float(value, precision, specs, buf);
- if (precision < 0) {
+ if (!is_constant_evaluated() && precision < 0) {
// Use Dragonbox for the shortest format.
if (specs.binary32) {
auto dec = dragonbox::to_decimal(static_cast<float>(value));
return dec.exponent;
}
- // Use Grisu + Dragon4 for the given precision:
- // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
int exp = 0;
- const int min_exp = -60; // alpha in Grisu.
- int cached_exp10 = 0; // K in Grisu.
- fp normalized = normalize(fp(value));
- const auto cached_pow = get_cached_power(
- min_exp - (normalized.e + fp::significand_size), cached_exp10);
- normalized = normalized * cached_pow;
- // Limit precision to the maximum possible number of significant digits in an
- // IEEE754 double because we don't need to generate zeros.
- const int max_double_digits = 767;
- if (precision > max_double_digits) precision = max_double_digits;
- fixed_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
- if (grisu_gen_digits(normalized, 1, exp, handler) == digits::error) {
- exp += handler.size - cached_exp10 - 1;
- fallback_format(value, handler.precision, specs.binary32, buf, exp);
- } else {
- exp += handler.exp10;
- buf.try_resize(to_unsigned(handler.size));
+ bool use_dragon = true;
+ if (is_fast_float<Float>()) {
+ // Use Grisu + Dragon4 for the given precision:
+ // https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf.
+ const int min_exp = -60; // alpha in Grisu.
+ int cached_exp10 = 0; // K in Grisu.
+ fp normalized = normalize(fp(value));
+ const auto cached_pow = get_cached_power(
+ min_exp - (normalized.e + fp::num_significand_bits), cached_exp10);
+ normalized = normalized * cached_pow;
+ gen_digits_handler handler{buf.data(), 0, precision, -cached_exp10, fixed};
+ if (grisu_gen_digits(normalized, 1, exp, handler) != digits::error &&
+ !is_constant_evaluated()) {
+ exp += handler.exp10;
+ buf.try_resize(to_unsigned(handler.size));
+ use_dragon = false;
+ } else {
+ exp += handler.size - cached_exp10 - 1;
+ precision = handler.precision;
+ }
+ }
+ if (use_dragon) {
+ auto f = fp();
+ bool is_predecessor_closer =
+ specs.binary32 ? f.assign(static_cast<float>(value)) : f.assign(value);
+ // Limit precision to the maximum possible number of significant digits in
+ // an IEEE754 double because we don't need to generate zeros.
+ const int max_double_digits = 767;
+ if (precision > max_double_digits) precision = max_double_digits;
+ format_dragon(f, is_predecessor_closer, precision, buf, exp);
}
if (!fixed && !specs.showpoint) {
// Remove trailing zeros.
buf.try_resize(num_digits);
}
return exp;
-} // namespace detail
+}
template <typename T>
int snprintf_float(T value, int precision, float_specs specs,
};
FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) {
- for_each_codepoint(s, [this](uint32_t cp, int error) {
- if (error != 0) FMT_THROW(std::runtime_error("invalid utf8"));
+ for_each_codepoint(s, [this](uint32_t cp, string_view) {
+ if (cp == invalid_code_point) FMT_THROW(std::runtime_error("invalid utf8"));
if (cp <= 0xFFFF) {
buffer_.push_back(static_cast<wchar_t>(cp));
} else {
buffer_.push_back(static_cast<wchar_t>(0xD800 + (cp >> 10)));
buffer_.push_back(static_cast<wchar_t>(0xDC00 + (cp & 0x3FF)));
}
+ return true;
});
buffer_.push_back(0);
}
format_error_code(out, error_code, message);
}
-FMT_FUNC void detail::error_handler::on_error(const char* message) {
- FMT_THROW(format_error(message));
-}
-
FMT_FUNC void report_system_error(int error_code,
const char* message) FMT_NOEXCEPT {
report_error(format_system_error, error_code, message);
}
+// DEPRECATED!
+// This function is defined here and not inline for ABI compatiblity.
+FMT_FUNC void detail::error_handler::on_error(const char* message) {
+ throw_format_error(message);
+}
+
FMT_FUNC std::string vformat(string_view fmt, format_args args) {
// Don't optimize the "{}" case to keep the binary size small and because it
// can be better optimized in fmt::format anyway.
#include <system_error> // std::system_error
#include <utility> // std::swap
+#ifdef __cpp_lib_bit_cast
+# include <bit> // std::bitcast
+#endif
+
#include "core.h"
-#ifdef __INTEL_COMPILER
-# define FMT_ICC_VERSION __INTEL_COMPILER
-#elif defined(__ICL)
-# define FMT_ICC_VERSION __ICL
+#if FMT_GCC_VERSION
+# define FMT_GCC_VISIBILITY_HIDDEN __attribute__((visibility("hidden")))
#else
-# define FMT_ICC_VERSION 0
+# define FMT_GCC_VISIBILITY_HIDDEN
#endif
#ifdef __NVCC__
# define FMT_CATCH(x) if (false)
#endif
-#ifndef FMT_DEPRECATED
-# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900
-# define FMT_DEPRECATED [[deprecated]]
+#ifndef FMT_MAYBE_UNUSED
+# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused)
+# define FMT_MAYBE_UNUSED [[maybe_unused]]
# else
-# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__)
-# define FMT_DEPRECATED __attribute__((deprecated))
-# elif FMT_MSC_VER
-# define FMT_DEPRECATED __declspec(deprecated)
-# else
-# define FMT_DEPRECATED /* deprecated */
-# endif
+# define FMT_MAYBE_UNUSED
# endif
#endif
#endif
// __builtin_clz is broken in clang with Microsoft CodeGen:
-// https://github.com/fmtlib/fmt/issues/519
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clz)) && !FMT_MSC_VER
-# define FMT_BUILTIN_CLZ(n) __builtin_clz(n)
-#endif
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_clzll)) && !FMT_MSC_VER
-# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)
-#endif
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctz))
-# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n)
+// https://github.com/fmtlib/fmt/issues/519.
+#if !FMT_MSC_VER
+# if FMT_HAS_BUILTIN(__builtin_clz) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CLZ(n) __builtin_clz(n)
+# endif
+# if FMT_HAS_BUILTIN(__builtin_clzll) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n)
+# endif
#endif
-#if (FMT_GCC_VERSION || FMT_HAS_BUILTIN(__builtin_ctzll))
-# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n)
+
+// __builtin_ctz is broken in Intel Compiler Classic on Windows:
+// https://github.com/fmtlib/fmt/issues/2510.
+#ifndef __ICL
+# if FMT_HAS_BUILTIN(__builtin_ctz) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CTZ(n) __builtin_ctz(n)
+# endif
+# if FMT_HAS_BUILTIN(__builtin_ctzll) || FMT_GCC_VERSION || FMT_ICC_VERSION
+# define FMT_BUILTIN_CTZLL(n) __builtin_ctzll(n)
+# endif
#endif
#if FMT_MSC_VER
namespace detail {
// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning.
# if !defined(__clang__)
-# pragma managed(push, off)
# pragma intrinsic(_BitScanForward)
# pragma intrinsic(_BitScanReverse)
# if defined(_WIN64)
return static_cast<int>(r);
}
# define FMT_BUILTIN_CTZLL(n) detail::ctzll(n)
-# if !defined(__clang__)
-# pragma managed(pop)
-# endif
} // namespace detail
FMT_END_NAMESPACE
#endif
+#ifdef FMT_HEADER_ONLY
+# define FMT_HEADER_ONLY_CONSTEXPR20 FMT_CONSTEXPR20
+#else
+# define FMT_HEADER_ONLY_CONSTEXPR20
+#endif
+
FMT_BEGIN_NAMESPACE
namespace detail {
-#if __cplusplus >= 202002L || \
- (__cplusplus >= 201709L && FMT_GCC_VERSION >= 1002)
-# define FMT_CONSTEXPR20 constexpr
-#else
-# define FMT_CONSTEXPR20
-#endif
+template <typename Streambuf> class formatbuf : public Streambuf {
+ private:
+ using char_type = typename Streambuf::char_type;
+ using streamsize = decltype(std::declval<Streambuf>().sputn(nullptr, 0));
+ using int_type = typename Streambuf::int_type;
+ using traits_type = typename Streambuf::traits_type;
+
+ buffer<char_type>& buffer_;
-// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't have
-// undefined behavior (e.g. due to type aliasing).
-// Example: uint64_t d = bit_cast<uint64_t>(2.718);
-template <typename Dest, typename Source>
-inline auto bit_cast(const Source& source) -> Dest {
- static_assert(sizeof(Dest) == sizeof(Source), "size mismatch");
- Dest dest;
- std::memcpy(&dest, &source, sizeof(dest));
- return dest;
+ public:
+ explicit formatbuf(buffer<char_type>& buf) : buffer_(buf) {}
+
+ protected:
+ // The put area is always empty. This makes the implementation simpler and has
+ // the advantage that the streambuf and the buffer are always in sync and
+ // sputc never writes into uninitialized memory. A disadvantage is that each
+ // call to sputc always results in a (virtual) call to overflow. There is no
+ // disadvantage here for sputn since this always results in a call to xsputn.
+
+ auto overflow(int_type ch) -> int_type override {
+ if (!traits_type::eq_int_type(ch, traits_type::eof()))
+ buffer_.push_back(static_cast<char_type>(ch));
+ return ch;
+ }
+
+ auto xsputn(const char_type* s, streamsize count) -> streamsize override {
+ buffer_.append(s, s + count);
+ return count;
+ }
+};
+
+// Implementation of std::bit_cast for pre-C++20.
+template <typename To, typename From>
+FMT_CONSTEXPR20 auto bit_cast(const From& from) -> To {
+ static_assert(sizeof(To) == sizeof(From), "size mismatch");
+#ifdef __cpp_lib_bit_cast
+ if (is_constant_evaluated()) return std::bit_cast<To>(from);
+#endif
+ auto to = To();
+ std::memcpy(&to, &from, sizeof(to));
+ return to;
}
inline auto is_big_endian() -> bool {
- const auto u = 1u;
+#ifdef _WIN32
+ return false;
+#elif defined(__BIG_ENDIAN__)
+ return true;
+#elif defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+ return __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__;
+#else
struct bytes {
- char data[sizeof(u)];
+ char data[sizeof(int)];
};
- return bit_cast<bytes>(u).data[0] == 0;
+ return bit_cast<bytes>(1).data[0] == 0;
+#endif
}
// A fallback implementation of uintptr_t for systems that lack it.
fallback_uintptr() = default;
explicit fallback_uintptr(const void* p) {
*this = bit_cast<fallback_uintptr>(p);
- if (is_big_endian()) {
+ if (const_check(is_big_endian())) {
for (size_t i = 0, j = sizeof(void*) - 1; i < j; ++i, --j)
std::swap(value[i], value[j]);
}
#if defined(_SECURE_SCL) && _SECURE_SCL
// Make a checked iterator to avoid MSVC warnings.
template <typename T> using checked_ptr = stdext::checked_array_iterator<T*>;
-template <typename T> auto make_checked(T* p, size_t size) -> checked_ptr<T> {
+template <typename T>
+constexpr auto make_checked(T* p, size_t size) -> checked_ptr<T> {
return {p, size};
}
#else
template <typename T> using checked_ptr = T*;
-template <typename T> inline auto make_checked(T* p, size_t) -> T* { return p; }
+template <typename T> constexpr auto make_checked(T* p, size_t) -> T* {
+ return p;
+}
#endif
// Attempts to reserve space for n extra characters in the output range.
return next;
}
+constexpr uint32_t invalid_code_point = ~uint32_t();
+
+// Invokes f(cp, sv) for every code point cp in s with sv being the string view
+// corresponding to the code point. cp is invalid_code_point on error.
template <typename F>
FMT_CONSTEXPR void for_each_codepoint(string_view s, F f) {
- auto decode = [f](const char* p) {
+ auto decode = [f](const char* buf_ptr, const char* ptr) {
auto cp = uint32_t();
auto error = 0;
- p = utf8_decode(p, &cp, &error);
- f(cp, error);
- return p;
+ auto end = utf8_decode(buf_ptr, &cp, &error);
+ bool result = f(error ? invalid_code_point : cp,
+ string_view(ptr, to_unsigned(end - buf_ptr)));
+ return result ? end : nullptr;
};
auto p = s.data();
const size_t block_size = 4; // utf8_decode always reads blocks of 4 chars.
if (s.size() >= block_size) {
- for (auto end = p + s.size() - block_size + 1; p < end;) p = decode(p);
+ for (auto end = p + s.size() - block_size + 1; p < end;) {
+ p = decode(p, p);
+ if (!p) return;
+ }
}
if (auto num_chars_left = s.data() + s.size() - p) {
char buf[2 * block_size - 1] = {};
copy_str<char>(p, p + num_chars_left, buf);
- p = buf;
+ const char* buf_ptr = buf;
do {
- p = decode(p);
- } while (p - buf < num_chars_left);
+ auto end = decode(buf_ptr, p);
+ if (!end) return;
+ p += end - buf_ptr;
+ buf_ptr = end;
+ } while (buf_ptr - buf < num_chars_left);
}
}
// It is not a lambda for compatibility with C++14.
struct count_code_points {
size_t* count;
- FMT_CONSTEXPR void operator()(uint32_t cp, int error) const {
+ FMT_CONSTEXPR auto operator()(uint32_t cp, string_view) const -> bool {
*count += detail::to_unsigned(
1 +
- (error == 0 && cp >= 0x1100 &&
+ (cp >= 0x1100 &&
(cp <= 0x115f || // Hangul Jamo init. consonants
cp == 0x2329 || // LEFT-POINTING ANGLE BRACKET
cp == 0x232a || // RIGHT-POINTING ANGLE BRACKET
(cp >= 0x1f300 && cp <= 0x1f64f) ||
// Supplemental Symbols and Pictographs:
(cp >= 0x1f900 && cp <= 0x1f9ff))));
+ return true;
}
};
for_each_codepoint(s, count_code_points{&num_code_points});
return s.size();
}
-template <typename T>
-using is_fast_float = bool_constant<std::numeric_limits<T>::is_iec559 &&
- sizeof(T) <= sizeof(double)>;
+template <typename T, bool = std::is_floating_point<T>::value>
+struct is_fast_float : bool_constant<std::numeric_limits<T>::is_iec559 &&
+ sizeof(T) <= sizeof(double)> {};
+template <typename T> struct is_fast_float<T, false> : std::false_type {};
#ifndef FMT_USE_FULL_CACHE_DRAGONBOX
# define FMT_USE_FULL_CACHE_DRAGONBOX 0
**Example**::
- fmt::memory_buffer out;
- format_to(out, "The answer is {}.", 42);
+ auto out = fmt::memory_buffer();
+ format_to(std::back_inserter(out), "The answer is {}.", 42);
This will append the following output to the ``out`` object:
Allocator alloc_;
// Deallocate memory allocated by the buffer.
- void deallocate() {
+ FMT_CONSTEXPR20 void deallocate() {
T* data = this->data();
if (data != store_) alloc_.deallocate(data, this->capacity());
}
protected:
- void grow(size_t size) final FMT_OVERRIDE;
+ FMT_CONSTEXPR20 void grow(size_t size) override;
public:
using value_type = T;
using const_reference = const T&;
- explicit basic_memory_buffer(const Allocator& alloc = Allocator())
+ FMT_CONSTEXPR20 explicit basic_memory_buffer(
+ const Allocator& alloc = Allocator())
: alloc_(alloc) {
this->set(store_, SIZE);
+ if (detail::is_constant_evaluated()) {
+ detail::fill_n(store_, SIZE, T{});
+ }
}
- ~basic_memory_buffer() { deallocate(); }
+ FMT_CONSTEXPR20 ~basic_memory_buffer() { deallocate(); }
private:
// Move data from other to this buffer.
- void move(basic_memory_buffer& other) {
+ FMT_CONSTEXPR20 void move(basic_memory_buffer& other) {
alloc_ = std::move(other.alloc_);
T* data = other.data();
size_t size = other.size(), capacity = other.capacity();
if (data == other.store_) {
this->set(store_, capacity);
- std::uninitialized_copy(other.store_, other.store_ + size,
- detail::make_checked(store_, capacity));
+ if (detail::is_constant_evaluated()) {
+ detail::copy_str<T>(other.store_, other.store_ + size,
+ detail::make_checked(store_, capacity));
+ } else {
+ std::uninitialized_copy(other.store_, other.store_ + size,
+ detail::make_checked(store_, capacity));
+ }
} else {
this->set(data, capacity);
// Set pointer to the inline array so that delete is not called
of the other object to it.
\endrst
*/
- basic_memory_buffer(basic_memory_buffer&& other) FMT_NOEXCEPT { move(other); }
+ FMT_CONSTEXPR20 basic_memory_buffer(basic_memory_buffer&& other)
+ FMT_NOEXCEPT {
+ move(other);
+ }
/**
\rst
Resizes the buffer to contain *count* elements. If T is a POD type new
elements may not be initialized.
*/
- void resize(size_t count) { this->try_resize(count); }
+ FMT_CONSTEXPR20 void resize(size_t count) { this->try_resize(count); }
/** Increases the buffer capacity to *new_capacity*. */
void reserve(size_t new_capacity) { this->try_reserve(new_capacity); }
};
template <typename T, size_t SIZE, typename Allocator>
-void basic_memory_buffer<T, SIZE, Allocator>::grow(size_t size) {
+FMT_CONSTEXPR20 void basic_memory_buffer<T, SIZE, Allocator>::grow(
+ size_t size) {
#ifdef FMT_FUZZ
if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much");
#endif
format_error& operator=(const format_error&) = default;
format_error(format_error&&) = default;
format_error& operator=(format_error&&) = default;
- ~format_error() FMT_NOEXCEPT FMT_OVERRIDE FMT_MSC_DEFAULT;
+ ~format_error() FMT_NOEXCEPT override FMT_MSC_DEFAULT;
};
/**
FMT_BEGIN_DETAIL_NAMESPACE
-inline void throw_format_error(const char* message) {
- FMT_THROW(format_error(message));
-}
-
template <typename T> struct is_integral : std::is_integral<T> {};
template <> struct is_integral<int128_t> : std::true_type {};
template <> struct is_integral<uint128_t> : std::true_type {};
(factor)*1000000, (factor)*10000000, (factor)*100000000, \
(factor)*1000000000
-// Static data is placed in this class template for the header-only config.
-template <typename T = void> struct basic_data {
- // log10(2) = 0x0.4d104d427de7fbcc...
- static const uint64_t log10_2_significand = 0x4d104d427de7fbcc;
-
- // GCC generates slightly better code for pairs than chars.
- FMT_API static constexpr const char digits[100][2] = {
- {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
- {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
- {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
- {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
- {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
- {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
- {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
- {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
- {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
- {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
- {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
- {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
- {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
- {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
- {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
- {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
- {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
-
- FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
- FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
- FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
- 0x1000000u | ' '};
- FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
- 0};
- FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
- 0};
-};
+// Converts value in the range [0, 100) to a string.
+constexpr const char* digits2(size_t value) {
+ // GCC generates slightly better code when value is pointer-size.
+ return &"0001020304050607080910111213141516171819"
+ "2021222324252627282930313233343536373839"
+ "4041424344454647484950515253545556575859"
+ "6061626364656667686970717273747576777879"
+ "8081828384858687888990919293949596979899"[value * 2];
+}
-#ifdef FMT_SHARED
-// Required for -flto, -fivisibility=hidden and -shared to work
-extern template struct basic_data<void>;
+// Sign is a template parameter to workaround a bug in gcc 4.8.
+template <typename Char, typename Sign> constexpr Char sign(Sign s) {
+#if !FMT_GCC_VERSION || FMT_GCC_VERSION >= 604
+ static_assert(std::is_same<Sign, sign_t>::value, "");
#endif
-
-// This is a struct rather than an alias to avoid shadowing warnings in gcc.
-struct data : basic_data<> {};
+ return static_cast<Char>("\0-+ "[s]);
+}
template <typename T> FMT_CONSTEXPR auto count_digits_fallback(T n) -> int {
int count = 1;
}
#endif
+#ifdef FMT_BUILTIN_CLZLL
+// It is a separate function rather than a part of count_digits to workaround
+// the lack of static constexpr in constexpr functions.
+inline auto do_count_digits(uint64_t n) -> int {
+ // This has comparable performance to the version by Kendall Willets
+ // (https://github.com/fmtlib/format-benchmark/blob/master/digits10)
+ // but uses smaller tables.
+ // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
+ static constexpr uint8_t bsr2log10[] = {
+ 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
+ 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
+ 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
+ 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
+ auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];
+ static constexpr const uint64_t zero_or_powers_of_10[] = {
+ 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
+ 10000000000000000000ULL};
+ return t - (n < zero_or_powers_of_10[t]);
+}
+#endif
+
// Returns the number of decimal digits in n. Leading zeros are not counted
// except for n == 0 in which case count_digits returns 1.
FMT_CONSTEXPR20 inline auto count_digits(uint64_t n) -> int {
#ifdef FMT_BUILTIN_CLZLL
if (!is_constant_evaluated()) {
- // https://github.com/fmtlib/format-benchmark/blob/master/digits10
- // Maps bsr(n) to ceil(log10(pow(2, bsr(n) + 1) - 1)).
- constexpr uint16_t bsr2log10[] = {
- 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5,
- 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
- 10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
- 15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
- auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];
- constexpr const uint64_t zero_or_powers_of_10[] = {
- 0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
- 10000000000000000000ULL};
- return t - (n < zero_or_powers_of_10[t]);
+ return do_count_digits(n);
}
#endif
return count_digits_fallback(n);
if (num_bits<UInt>() == 32)
return (FMT_BUILTIN_CLZ(static_cast<uint32_t>(n) | 1) ^ 31) / BITS + 1;
#endif
- int num_digits = 0;
- do {
- ++num_digits;
- } while ((n >>= BITS) != 0);
- return num_digits;
+ // Lambda avoids unreachable code warnings from NVHPC.
+ return [](UInt m) {
+ int num_digits = 0;
+ do {
+ ++num_digits;
+ } while ((m >>= BITS) != 0);
+ return num_digits;
+ }(n);
}
template <> auto count_digits<4>(detail::fallback_uintptr n) -> int;
+#ifdef FMT_BUILTIN_CLZ
// It is a separate function rather than a part of count_digits to workaround
// the lack of static constexpr in constexpr functions.
-FMT_INLINE uint64_t count_digits_inc(int n) {
- // An optimization by Kendall Willets from https://bit.ly/3uOIQrB.
- // This increments the upper 32 bits (log10(T) - 1) when >= T is added.
-#define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T)
+FMT_INLINE auto do_count_digits(uint32_t n) -> int {
+// An optimization by Kendall Willets from https://bit.ly/3uOIQrB.
+// This increments the upper 32 bits (log10(T) - 1) when >= T is added.
+# define FMT_INC(T) (((sizeof(# T) - 1ull) << 32) - T)
static constexpr uint64_t table[] = {
FMT_INC(0), FMT_INC(0), FMT_INC(0), // 8
FMT_INC(10), FMT_INC(10), FMT_INC(10), // 64
FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000), // 1024M
FMT_INC(1000000000), FMT_INC(1000000000) // 4B
};
- return table[n];
+ auto inc = table[FMT_BUILTIN_CLZ(n | 1) ^ 31];
+ return static_cast<int>((n + inc) >> 32);
}
+#endif
// Optional version of count_digits for better performance on 32-bit platforms.
FMT_CONSTEXPR20 inline auto count_digits(uint32_t n) -> int {
#ifdef FMT_BUILTIN_CLZ
if (!is_constant_evaluated()) {
- auto inc = count_digits_inc(FMT_BUILTIN_CLZ(n | 1) ^ 31);
- return static_cast<int>((n + inc) >> 32);
+ return do_count_digits(n);
}
#endif
return count_digits_fallback(n);
}
// Copies two characters from src to dst.
-template <typename Char> void copy2(Char* dst, const char* src) {
+template <typename Char>
+FMT_CONSTEXPR20 FMT_INLINE void copy2(Char* dst, const char* src) {
+ if (!is_constant_evaluated() && sizeof(Char) == sizeof(char)) {
+ memcpy(dst, src, 2);
+ return;
+ }
*dst++ = static_cast<Char>(*src++);
*dst = static_cast<Char>(*src);
}
-FMT_INLINE void copy2(char* dst, const char* src) { memcpy(dst, src, 2); }
template <typename Iterator> struct format_decimal_result {
Iterator begin;
FMT_ASSERT(size >= count_digits(value), "invalid digit count");
out += size;
Char* end = out;
- if (is_constant_evaluated()) {
- while (value >= 10) {
- *--out = static_cast<Char>('0' + value % 10);
- value /= 10;
- }
- *--out = static_cast<Char>('0' + value);
- return {out, end};
- }
while (value >= 100) {
// Integer division is slow so do it for a group of two digits instead
// of for every digit. The idea comes from the talk by Alexandrescu
// "Three Optimization Tips for C++". See speed-test for a comparison.
out -= 2;
- copy2(out, data::digits[value % 100]);
+ copy2(out, digits2(static_cast<size_t>(value % 100)));
value /= 100;
}
if (value < 10) {
return {out, end};
}
out -= 2;
- copy2(out, data::digits[value]);
+ copy2(out, digits2(static_cast<size_t>(value)));
return {out, end};
}
buffer += num_digits;
Char* end = buffer;
do {
- const char* digits = upper ? "0123456789ABCDEF" : data::hex_digits;
+ const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
unsigned digit = (value & ((1 << BASE_BITS) - 1));
*--buffer = static_cast<Char>(BASE_BITS < 4 ? static_cast<char>('0' + digit)
: digits[digit]);
auto p = buffer;
for (int i = 0; i < char_digits; ++i) {
unsigned digit = (value & ((1 << BASE_BITS) - 1));
- *--p = static_cast<Char>(data::hex_digits[digit]);
+ *--p = static_cast<Char>("0123456789abcdef"[digit]);
value >>= BASE_BITS;
}
}
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
template <typename Char, typename It>
-auto write_exponent(int exp, It it) -> It {
+FMT_CONSTEXPR auto write_exponent(int exp, It it) -> It {
FMT_ASSERT(-10000 < exp && exp < 10000, "exponent out of range");
if (exp < 0) {
*it++ = static_cast<Char>('-');
*it++ = static_cast<Char>('+');
}
if (exp >= 100) {
- const char* top = data::digits[exp / 100];
+ const char* top = digits2(to_unsigned(exp / 100));
if (exp >= 1000) *it++ = static_cast<Char>(top[0]);
*it++ = static_cast<Char>(top[1]);
exp %= 100;
}
- const char* d = data::digits[exp];
+ const char* d = digits2(to_unsigned(exp));
*it++ = static_cast<Char>(d[0]);
*it++ = static_cast<Char>(d[1]);
return it;
}
template <typename T>
-auto format_float(T value, int precision, float_specs specs, buffer<char>& buf)
- -> int;
+FMT_HEADER_ONLY_CONSTEXPR20 auto format_float(T value, int precision,
+ float_specs specs,
+ buffer<char>& buf) -> int;
// Formats a floating-point number with snprintf.
template <typename T>
auto snprintf_float(T value, int precision, float_specs specs,
buffer<char>& buf) -> int;
-template <typename T> auto promote_float(T value) -> T { return value; }
-inline auto promote_float(float value) -> double {
+template <typename T> constexpr auto promote_float(T value) -> T {
+ return value;
+}
+constexpr auto promote_float(float value) -> double {
return static_cast<double>(value);
}
static_assert(align == align::left || align == align::right, "");
unsigned spec_width = to_unsigned(specs.width);
size_t padding = spec_width > width ? spec_width - width : 0;
- auto* shifts = align == align::left ? data::left_padding_shifts
- : data::right_padding_shifts;
+ // Shifts are encoded as string literals because static constexpr is not
+ // supported in constexpr functions.
+ auto* shifts = align == align::left ? "\x1f\x1f\x00\x01" : "\x00\x1f\x00\x01";
size_t left_padding = padding >> shifts[specs.align];
size_t right_padding = padding - left_padding;
auto it = reserve(out, size + padding * specs.fill.size());
});
}
+template <typename Char> class digit_grouping {
+ private:
+ thousands_sep_result<Char> sep_;
+
+ struct next_state {
+ std::string::const_iterator group;
+ int pos;
+ };
+ next_state initial_state() const { return {sep_.grouping.begin(), 0}; }
+
+ // Returns the next digit group separator position.
+ int next(next_state& state) const {
+ if (!sep_.thousands_sep) return max_value<int>();
+ if (state.group == sep_.grouping.end())
+ return state.pos += sep_.grouping.back();
+ if (*state.group <= 0 || *state.group == max_value<char>())
+ return max_value<int>();
+ state.pos += *state.group++;
+ return state.pos;
+ }
+
+ public:
+ explicit digit_grouping(locale_ref loc, bool localized = true) {
+ if (localized)
+ sep_ = thousands_sep<Char>(loc);
+ else
+ sep_.thousands_sep = Char();
+ }
+ explicit digit_grouping(thousands_sep_result<Char> sep) : sep_(sep) {}
+
+ Char separator() const { return sep_.thousands_sep; }
+
+ int count_separators(int num_digits) const {
+ int count = 0;
+ auto state = initial_state();
+ while (num_digits > next(state)) ++count;
+ return count;
+ }
+
+ // Applies grouping to digits and write the output to out.
+ template <typename Out, typename C>
+ Out apply(Out out, basic_string_view<C> digits) const {
+ auto num_digits = static_cast<int>(digits.size());
+ auto separators = basic_memory_buffer<int>();
+ separators.push_back(0);
+ auto state = initial_state();
+ while (int i = next(state)) {
+ if (i >= num_digits) break;
+ separators.push_back(i);
+ }
+ for (int i = 0, sep_index = static_cast<int>(separators.size() - 1);
+ i < num_digits; ++i) {
+ if (num_digits - i == separators[sep_index]) {
+ *out++ = separator();
+ --sep_index;
+ }
+ *out++ = static_cast<Char>(digits[to_unsigned(i)]);
+ }
+ return out;
+ }
+};
+
template <typename OutputIt, typename UInt, typename Char>
-auto write_int_localized(OutputIt& out, UInt value, unsigned prefix,
- const basic_format_specs<Char>& specs, locale_ref loc)
- -> bool {
+auto write_int_localized(OutputIt out, UInt value, unsigned prefix,
+ const basic_format_specs<Char>& specs,
+ const digit_grouping<Char>& grouping) -> OutputIt {
static_assert(std::is_same<uint64_or_128_t<UInt>, UInt>::value, "");
- const auto sep_size = 1;
- auto ts = thousands_sep<Char>(loc);
- if (!ts.thousands_sep) return false;
int num_digits = count_digits(value);
- int size = num_digits, n = num_digits;
- const std::string& groups = ts.grouping;
- std::string::const_iterator group = groups.cbegin();
- while (group != groups.cend() && n > *group && *group > 0 &&
- *group != max_value<char>()) {
- size += sep_size;
- n -= *group;
- ++group;
- }
- if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back());
char digits[40];
format_decimal(digits, value, num_digits);
- basic_memory_buffer<Char> buffer;
- if (prefix != 0) ++size;
- const auto usize = to_unsigned(size);
- buffer.resize(usize);
- basic_string_view<Char> s(&ts.thousands_sep, sep_size);
- // Index of a decimal digit with the least significant digit having index 0.
- int digit_index = 0;
- group = groups.cbegin();
- auto p = buffer.data() + size - 1;
- for (int i = num_digits - 1; i > 0; --i) {
- *p-- = static_cast<Char>(digits[i]);
- if (*group <= 0 || ++digit_index % *group != 0 ||
- *group == max_value<char>())
- continue;
- if (group + 1 != groups.cend()) {
- digit_index = 0;
- ++group;
- }
- std::uninitialized_copy(s.data(), s.data() + s.size(),
- make_checked(p, s.size()));
- p -= s.size();
- }
- *p-- = static_cast<Char>(*digits);
- if (prefix != 0) *p = static_cast<Char>(prefix);
- auto data = buffer.data();
- out = write_padded<align::right>(
- out, specs, usize, usize, [=](reserve_iterator<OutputIt> it) {
- return copy_str<Char>(data, data + size, it);
+ unsigned size = to_unsigned((prefix != 0 ? 1 : 0) + num_digits +
+ grouping.count_separators(num_digits));
+ return write_padded<align::right>(
+ out, specs, size, size, [&](reserve_iterator<OutputIt> it) {
+ if (prefix != 0) *it++ = static_cast<Char>(prefix);
+ return grouping.apply(it, string_view(digits, to_unsigned(num_digits)));
});
+}
+
+template <typename OutputIt, typename UInt, typename Char>
+auto write_int_localized(OutputIt& out, UInt value, unsigned prefix,
+ const basic_format_specs<Char>& specs, locale_ref loc)
+ -> bool {
+ auto grouping = digit_grouping<Char>(loc);
+ out = write_int_localized(out, value, prefix, specs, grouping);
return true;
}
prefix = 0x01000000 | '-';
abs_value = 0 - abs_value;
} else {
- prefix = data::prefixes[sign];
+ constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
+ 0x1000000u | ' '};
+ prefix = prefixes[sign];
}
return {abs_value, prefix};
}
static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, "");
auto abs_value = arg.abs_value;
auto prefix = arg.prefix;
- auto utype = static_cast<unsigned>(specs.type);
switch (specs.type) {
- case 0:
- case 'd': {
+ case presentation_type::none:
+ case presentation_type::dec: {
if (specs.localized &&
write_int_localized(out, static_cast<uint64_or_128_t<T>>(abs_value),
prefix, specs, loc)) {
return format_decimal<Char>(it, abs_value, num_digits).end;
});
}
- case 'x':
- case 'X': {
- if (specs.alt) prefix_append(prefix, (utype << 8) | '0');
- bool upper = specs.type != 'x';
+ case presentation_type::hex_lower:
+ case presentation_type::hex_upper: {
+ bool upper = specs.type == presentation_type::hex_upper;
+ if (specs.alt)
+ prefix_append(prefix, unsigned(upper ? 'X' : 'x') << 8 | '0');
int num_digits = count_digits<4>(abs_value);
return write_int(
out, num_digits, prefix, specs, [=](reserve_iterator<OutputIt> it) {
return format_uint<4, Char>(it, abs_value, num_digits, upper);
});
}
- case 'b':
- case 'B': {
- if (specs.alt) prefix_append(prefix, (utype << 8) | '0');
+ case presentation_type::bin_lower:
+ case presentation_type::bin_upper: {
+ bool upper = specs.type == presentation_type::bin_upper;
+ if (specs.alt)
+ prefix_append(prefix, unsigned(upper ? 'B' : 'b') << 8 | '0');
int num_digits = count_digits<1>(abs_value);
return write_int(out, num_digits, prefix, specs,
[=](reserve_iterator<OutputIt> it) {
return format_uint<1, Char>(it, abs_value, num_digits);
});
}
- case 'o': {
+ case presentation_type::oct: {
int num_digits = count_digits<3>(abs_value);
- if (specs.alt && specs.precision <= num_digits && abs_value != 0) {
- // Octal prefix '0' is counted as a digit, so only add it if precision
- // is not greater than the number of digits.
+ // Octal prefix '0' is counted as a digit, so only add it if precision
+ // is not greater than the number of digits.
+ if (specs.alt && specs.precision <= num_digits && abs_value != 0)
prefix_append(prefix, '0');
- }
return write_int(out, num_digits, prefix, specs,
[=](reserve_iterator<OutputIt> it) {
return format_uint<3, Char>(it, abs_value, num_digits);
});
}
- case 'c':
+ case presentation_type::chr:
return write_char(out, static_cast<Char>(abs_value), specs);
default:
- FMT_THROW(format_error("invalid type specifier"));
+ throw_format_error("invalid type specifier");
}
return out;
}
+template <typename Char, typename OutputIt, typename T>
+FMT_CONSTEXPR FMT_NOINLINE auto write_int_noinline(
+ OutputIt out, write_int_arg<T> arg, const basic_format_specs<Char>& specs,
+ locale_ref loc) -> OutputIt {
+ return write_int(out, arg, specs, loc);
+}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_integral<T>::value &&
!std::is_same<T, bool>::value &&
std::is_same<OutputIt, buffer_appender<Char>>::value)>
-FMT_CONSTEXPR auto write(OutputIt out, T value,
- const basic_format_specs<Char>& specs, locale_ref loc)
- -> OutputIt {
- return write_int(out, make_write_int_arg(value, specs.sign), specs, loc);
+FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
+ const basic_format_specs<Char>& specs,
+ locale_ref loc) -> OutputIt {
+ return write_int_noinline(out, make_write_int_arg(value, specs.sign), specs,
+ loc);
}
// An inlined version of write used in format string compilation.
template <typename Char, typename OutputIt, typename T,
}
template <typename Char, typename OutputIt>
-auto write_nonfinite(OutputIt out, bool isinf, basic_format_specs<Char> specs,
- const float_specs& fspecs) -> OutputIt {
+FMT_CONSTEXPR20 auto write_nonfinite(OutputIt out, bool isinf,
+ basic_format_specs<Char> specs,
+ const float_specs& fspecs) -> OutputIt {
auto str =
isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan");
constexpr size_t str_size = 3;
specs.fill.size() == 1 && *specs.fill.data() == static_cast<Char>('0');
if (is_zero_fill) specs.fill[0] = static_cast<Char>(' ');
return write_padded(out, specs, size, [=](reserve_iterator<OutputIt> it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
return copy_str<Char>(str, str + str_size, it);
});
}
int exponent;
};
-inline auto get_significand_size(const big_decimal_fp& fp) -> int {
+constexpr auto get_significand_size(const big_decimal_fp& fp) -> int {
return fp.significand_size;
}
template <typename T>
}
template <typename Char, typename OutputIt>
-inline auto write_significand(OutputIt out, const char* significand,
- int& significand_size) -> OutputIt {
+constexpr auto write_significand(OutputIt out, const char* significand,
+ int significand_size) -> OutputIt {
return copy_str<Char>(significand, significand + significand_size, out);
}
template <typename Char, typename OutputIt, typename UInt>
int significand_size) -> OutputIt {
return format_decimal<Char>(out, significand, significand_size).end;
}
+template <typename Char, typename OutputIt, typename T, typename Grouping>
+FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
+ int significand_size, int exponent,
+ const Grouping& grouping) -> OutputIt {
+ if (!grouping.separator()) {
+ out = write_significand<Char>(out, significand, significand_size);
+ return detail::fill_n(out, exponent, static_cast<Char>('0'));
+ }
+ auto buffer = memory_buffer();
+ write_significand<char>(appender(buffer), significand, significand_size);
+ detail::fill_n(appender(buffer), exponent, '0');
+ return grouping.apply(out, string_view(buffer.data(), buffer.size()));
+}
template <typename Char, typename UInt,
FMT_ENABLE_IF(std::is_integral<UInt>::value)>
int integral_size, Char decimal_point) -> Char* {
if (!decimal_point)
return format_decimal(out, significand, significand_size).end;
- auto end = format_decimal(out + 1, significand, significand_size).end;
- if (integral_size == 1) {
- out[0] = out[1];
- } else {
- std::uninitialized_copy_n(out + 1, integral_size,
- make_checked(out, to_unsigned(integral_size)));
+ out += significand_size + 1;
+ Char* end = out;
+ int floating_size = significand_size - integral_size;
+ for (int i = floating_size / 2; i > 0; --i) {
+ out -= 2;
+ copy2(out, digits2(significand % 100));
+ significand /= 100;
+ }
+ if (floating_size % 2 != 0) {
+ *--out = static_cast<Char>('0' + significand % 10);
+ significand /= 10;
}
- out[integral_size] = decimal_point;
+ *--out = decimal_point;
+ format_decimal(out - integral_size, significand, integral_size);
return end;
}
}
template <typename OutputIt, typename Char>
-inline auto write_significand(OutputIt out, const char* significand,
- int significand_size, int integral_size,
- Char decimal_point) -> OutputIt {
+FMT_CONSTEXPR auto write_significand(OutputIt out, const char* significand,
+ int significand_size, int integral_size,
+ Char decimal_point) -> OutputIt {
out = detail::copy_str_noinline<Char>(significand,
significand + integral_size, out);
if (!decimal_point) return out;
significand + significand_size, out);
}
-template <typename OutputIt, typename DecimalFP, typename Char>
-auto write_float(OutputIt out, const DecimalFP& fp,
- const basic_format_specs<Char>& specs, float_specs fspecs,
- Char decimal_point) -> OutputIt {
+template <typename OutputIt, typename Char, typename T, typename Grouping>
+FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
+ int significand_size, int integral_size,
+ Char decimal_point,
+ const Grouping& grouping) -> OutputIt {
+ if (!grouping.separator()) {
+ return write_significand(out, significand, significand_size, integral_size,
+ decimal_point);
+ }
+ auto buffer = basic_memory_buffer<Char>();
+ write_significand(buffer_appender<Char>(buffer), significand,
+ significand_size, integral_size, decimal_point);
+ grouping.apply(
+ out, basic_string_view<Char>(buffer.data(), to_unsigned(integral_size)));
+ return detail::copy_str_noinline<Char>(buffer.data() + integral_size,
+ buffer.end(), out);
+}
+
+template <typename OutputIt, typename DecimalFP, typename Char,
+ typename Grouping = digit_grouping<Char>>
+FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& fp,
+ const basic_format_specs<Char>& specs,
+ float_specs fspecs, locale_ref loc)
+ -> OutputIt {
auto significand = fp.significand;
int significand_size = get_significand_size(fp);
- static const Char zero = static_cast<Char>('0');
+ constexpr Char zero = static_cast<Char>('0');
auto sign = fspecs.sign;
size_t size = to_unsigned(significand_size) + (sign ? 1 : 0);
using iterator = reserve_iterator<OutputIt>;
+ Char decimal_point =
+ fspecs.locale ? detail::decimal_point<Char>(loc) : static_cast<Char>('.');
+
int output_exp = fp.exponent + significand_size - 1;
auto use_exp_format = [=]() {
if (fspecs.format == float_format::exp) return true;
size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);
char exp_char = fspecs.upper ? 'E' : 'e';
auto write = [=](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
// Insert a decimal point after the first digit and add an exponent.
it = write_significand(it, significand, significand_size, 1,
decimal_point);
if (num_zeros <= 0 && fspecs.format != float_format::fixed) num_zeros = 1;
if (num_zeros > 0) size += to_unsigned(num_zeros) + 1;
}
+ auto grouping = Grouping(loc, fspecs.locale);
+ size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
- it = write_significand<Char>(it, significand, significand_size);
- it = detail::fill_n(it, fp.exponent, zero);
+ if (sign) *it++ = detail::sign<Char>(sign);
+ it = write_significand<Char>(it, significand, significand_size,
+ fp.exponent, grouping);
if (!fspecs.showpoint) return it;
*it++ = decimal_point;
return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
// 1234e-2 -> 12.34[0+]
int num_zeros = fspecs.showpoint ? fspecs.precision - significand_size : 0;
size += 1 + to_unsigned(num_zeros > 0 ? num_zeros : 0);
+ auto grouping = Grouping(loc, fspecs.locale);
+ size += to_unsigned(grouping.count_separators(significand_size));
return write_padded<align::right>(out, specs, size, [&](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
it = write_significand(it, significand, significand_size, exp,
- decimal_point);
+ decimal_point, grouping);
return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
});
}
bool pointy = num_zeros != 0 || significand_size != 0 || fspecs.showpoint;
size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);
return write_padded<align::right>(out, specs, size, [&](iterator it) {
- if (sign) *it++ = static_cast<Char>(data::signs[sign]);
+ if (sign) *it++ = detail::sign<Char>(sign);
*it++ = zero;
if (!pointy) return it;
*it++ = decimal_point;
});
}
+template <typename Char> class fallback_digit_grouping {
+ public:
+ constexpr fallback_digit_grouping(locale_ref, bool) {}
+
+ constexpr Char separator() const { return Char(); }
+
+ constexpr int count_separators(int) const { return 0; }
+
+ template <typename Out, typename C>
+ constexpr Out apply(Out out, basic_string_view<C>) const {
+ return out;
+ }
+};
+
+template <typename OutputIt, typename DecimalFP, typename Char>
+FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& fp,
+ const basic_format_specs<Char>& specs,
+ float_specs fspecs, locale_ref loc)
+ -> OutputIt {
+ if (is_constant_evaluated()) {
+ return do_write_float<OutputIt, DecimalFP, Char,
+ fallback_digit_grouping<Char>>(out, fp, specs, fspecs,
+ loc);
+ } else {
+ return do_write_float(out, fp, specs, fspecs, loc);
+ }
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_CONSTEXPR20 bool isinf(T value) {
+ if (is_constant_evaluated()) {
+#if defined(__cpp_if_constexpr)
+ if constexpr (std::numeric_limits<double>::is_iec559) {
+ auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
+ constexpr auto significand_bits =
+ dragonbox::float_info<double>::significand_bits;
+ return (bits & exponent_mask<double>()) &&
+ !(bits & ((uint64_t(1) << significand_bits) - 1));
+ }
+#endif
+ }
+ return std::isinf(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_CONSTEXPR20 bool isfinite(T value) {
+ if (is_constant_evaluated()) {
+#if defined(__cpp_if_constexpr)
+ if constexpr (std::numeric_limits<double>::is_iec559) {
+ auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
+ return (bits & exponent_mask<double>()) != exponent_mask<double>();
+ }
+#endif
+ }
+ return std::isfinite(value);
+}
+
+template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
+FMT_INLINE FMT_CONSTEXPR bool signbit(T value) {
+ if (is_constant_evaluated()) {
+#ifdef __cpp_if_constexpr
+ if constexpr (std::numeric_limits<double>::is_iec559) {
+ auto bits = detail::bit_cast<uint64_t>(static_cast<double>(value));
+ return (bits & (uint64_t(1) << (num_bits<uint64_t>() - 1))) != 0;
+ }
+#endif
+ }
+ return std::signbit(value);
+}
+
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(std::is_floating_point<T>::value)>
-auto write(OutputIt out, T value, basic_format_specs<Char> specs,
- locale_ref loc = {}) -> OutputIt {
+FMT_CONSTEXPR20 auto write(OutputIt out, T value,
+ basic_format_specs<Char> specs, locale_ref loc = {})
+ -> OutputIt {
if (const_check(!is_supported_floating_point(value))) return out;
float_specs fspecs = parse_float_type_spec(specs);
fspecs.sign = specs.sign;
- if (std::signbit(value)) { // value < 0 is false for NaN so use signbit.
+ if (detail::signbit(value)) { // value < 0 is false for NaN so use signbit.
fspecs.sign = sign::minus;
value = -value;
} else if (fspecs.sign == sign::minus) {
fspecs.sign = sign::none;
}
- if (!std::isfinite(value))
- return write_nonfinite(out, std::isinf(value), specs, fspecs);
+ if (!detail::isfinite(value))
+ return write_nonfinite(out, detail::isinf(value), specs, fspecs);
if (specs.align == align::numeric && fspecs.sign) {
auto it = reserve(out, 1);
- *it++ = static_cast<Char>(data::signs[fspecs.sign]);
+ *it++ = detail::sign<Char>(fspecs.sign);
out = base_iterator(out, it);
fspecs.sign = sign::none;
if (specs.width != 0) --specs.width;
memory_buffer buffer;
if (fspecs.format == float_format::hex) {
- if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]);
+ if (fspecs.sign) buffer.push_back(detail::sign<char>(fspecs.sign));
snprintf_float(promote_float(value), specs.precision, fspecs, buffer);
return write_bytes<align::right>(out, {buffer.data(), buffer.size()},
specs);
}
- int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6;
+ int precision = specs.precision >= 0 || specs.type == presentation_type::none
+ ? specs.precision
+ : 6;
if (fspecs.format == float_format::exp) {
if (precision == max_value<int>())
- FMT_THROW(format_error("number is too big"));
+ throw_format_error("number is too big");
else
++precision;
}
if (const_check(std::is_same<T, float>())) fspecs.binary32 = true;
- fspecs.use_grisu = is_fast_float<T>();
+ if (!is_fast_float<T>()) fspecs.fallback = true;
int exp = format_float(promote_float(value), precision, fspecs, buffer);
fspecs.precision = precision;
- Char point =
- fspecs.locale ? decimal_point<Char>(loc) : static_cast<Char>('.');
auto fp = big_decimal_fp{buffer.data(), static_cast<int>(buffer.size()), exp};
- return write_float(out, fp, specs, fspecs, point);
+ return write_float(out, fp, specs, fspecs, loc);
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_fast_float<T>::value)>
-auto write(OutputIt out, T value) -> OutputIt {
+FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt {
+ if (is_constant_evaluated()) {
+ return write(out, value, basic_format_specs<Char>());
+ }
+
if (const_check(!is_supported_floating_point(value))) return out;
using floaty = conditional_t<std::is_same<T, long double>::value, double, T>;
auto bits = bit_cast<uint>(value);
auto fspecs = float_specs();
- auto sign_bit = bits & (uint(1) << (num_bits<uint>() - 1));
- if (sign_bit != 0) {
+ if (detail::signbit(value)) {
fspecs.sign = sign::minus;
value = -value;
}
- static const auto specs = basic_format_specs<Char>();
+ constexpr auto specs = basic_format_specs<Char>();
uint mask = exponent_mask<floaty>();
if ((bits & mask) == mask)
return write_nonfinite(out, std::isinf(value), specs, fspecs);
auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
- return write_float(out, dec, specs, fspecs, static_cast<Char>('.'));
+ return write_float(out, dec, specs, fspecs, {});
}
template <typename Char, typename OutputIt, typename T,
return base_iterator(out, it);
}
-// FMT_ENABLE_IF() condition separated to workaround MSVC bug
+// FMT_ENABLE_IF() condition separated to workaround an MSVC bug.
template <
typename Char, typename OutputIt, typename T,
bool check =
FMT_CONSTEXPR auto write(OutputIt out, T value,
const basic_format_specs<Char>& specs = {},
locale_ref = {}) -> OutputIt {
- return specs.type && specs.type != 's'
+ return specs.type != presentation_type::none &&
+ specs.type != presentation_type::string
? write(out, value ? 1 : 0, specs, {})
: write_bytes(out, value ? "true" : "false", specs);
}
FMT_CONSTEXPR_CHAR_TRAITS auto write(OutputIt out, const Char* value)
-> OutputIt {
if (!value) {
- FMT_THROW(format_error("string pointer is null"));
+ throw_format_error("string pointer is null");
} else {
- auto length = std::char_traits<Char>::length(value);
- out = write(out, basic_string_view<Char>(value, length));
+ out = write(out, basic_string_view<Char>(value));
}
return out;
}
return write_ptr<Char>(out, to_uintptr(value), &specs);
}
-template <typename Char, typename OutputIt, typename T>
-FMT_CONSTEXPR auto write(OutputIt out, const T& value) ->
- typename std::enable_if<
- mapped_type_constant<T, basic_format_context<OutputIt, Char>>::value ==
- type::custom_type,
- OutputIt>::type {
- using context_type = basic_format_context<OutputIt, Char>;
+// A write overload that handles implicit conversions.
+template <typename Char, typename OutputIt, typename T,
+ typename Context = basic_format_context<OutputIt, Char>>
+FMT_CONSTEXPR auto write(OutputIt out, const T& value) -> enable_if_t<
+ std::is_class<T>::value && !is_string<T>::value &&
+ !std::is_same<T, Char>::value &&
+ !std::is_same<const T&,
+ decltype(arg_mapper<Context>().map(value))>::value,
+ OutputIt> {
+ return write<Char>(out, arg_mapper<Context>().map(value));
+}
+
+template <typename Char, typename OutputIt, typename T,
+ typename Context = basic_format_context<OutputIt, Char>>
+FMT_CONSTEXPR auto write(OutputIt out, const T& value)
+ -> enable_if_t<mapped_type_constant<T, Context>::value == type::custom_type,
+ OutputIt> {
using formatter_type =
- conditional_t<has_formatter<T, context_type>::value,
- typename context_type::template formatter_type<T>,
+ conditional_t<has_formatter<T, Context>::value,
+ typename Context::template formatter_type<T>,
fallback_formatter<T, Char>>;
- context_type ctx(out, {}, {});
+ auto ctx = Context(out, {}, {});
return formatter_type().format(value, ctx);
}
FMT_FORMAT_AS(Char*, const Char*);
FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>);
FMT_FORMAT_AS(std::nullptr_t, const void*);
+FMT_FORMAT_AS(detail::byte, unsigned char);
FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>);
template <typename Char>
}
};
+// group_digits_view is not derived from view because it copies the argument.
+template <typename T> struct group_digits_view { T value; };
+
+/**
+ \rst
+ Returns a view that formats an integer value using ',' as a locale-independent
+ thousands separator.
+
+ **Example**::
+
+ fmt::print("{}", fmt::group_digits(12345));
+ // Output: "12,345"
+ \endrst
+ */
+template <typename T> auto group_digits(T value) -> group_digits_view<T> {
+ return {value};
+}
+
+template <typename T> struct formatter<group_digits_view<T>> : formatter<T> {
+ private:
+ detail::dynamic_format_specs<char> specs_;
+
+ public:
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ using handler_type = detail::dynamic_specs_handler<ParseContext>;
+ detail::specs_checker<handler_type> handler(handler_type(specs_, ctx),
+ detail::type::int_type);
+ auto it = parse_format_specs(ctx.begin(), ctx.end(), handler);
+ detail::check_string_type_spec(specs_.type, ctx.error_handler());
+ return it;
+ }
+
+ template <typename FormatContext>
+ auto format(group_digits_view<T> t, FormatContext& ctx)
+ -> decltype(ctx.out()) {
+ detail::handle_dynamic_spec<detail::width_checker>(specs_.width,
+ specs_.width_ref, ctx);
+ detail::handle_dynamic_spec<detail::precision_checker>(
+ specs_.precision, specs_.precision_ref, ctx);
+ return detail::write_int_localized(
+ ctx.out(), static_cast<detail::uint64_or_128_t<T>>(t.value), 0, specs_,
+ detail::digit_grouping<char>({"\3", ','}));
+ }
+};
+
template <typename It, typename Sentinel, typename Char = char>
struct join_view : detail::view {
It begin;
template <typename It, typename Sentinel, typename Char>
struct formatter<join_view<It, Sentinel, Char>, Char> {
private:
- using value_type = typename std::iterator_traits<It>::value_type;
+ using value_type =
+#ifdef __cpp_lib_ranges
+ std::iter_value_t<It>;
+#else
+ typename std::iterator_traits<It>::value_type;
+#endif
using context = buffer_context<Char>;
using mapper = detail::arg_mapper<context>;
auto it = value.begin;
auto out = ctx.out();
if (it != value.end) {
- out = value_formatter_.format(map(*it++), ctx);
+ out = value_formatter_.format(map(*it), ctx);
+ ++it;
while (it != value.end) {
out = detail::copy_str<Char>(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
- out = value_formatter_.format(map(*it++), ctx);
+ out = value_formatter_.format(map(*it), ctx);
+ ++it;
}
}
return out;
};
/**
- Returns an object that formats the iterator range `[begin, end)` with
- elements separated by `sep`.
+ 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> {
/**
\rst
- Returns an object that formats `range` with elements separated by `sep`.
+ Returns a view that formats `range` with elements separated by `sep`.
**Example**::
}
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
-inline auto to_string(T value) -> std::string {
+FMT_NODISCARD inline auto to_string(T value) -> std::string {
// The buffer should be large enough to store the number including the sign
// or "false" for bool.
constexpr int max_size = detail::digits10<T>() + 2;
}
template <typename Char, size_t SIZE>
-auto to_string(const basic_memory_buffer<Char, SIZE>& buf)
+FMT_NODISCARD auto to_string(const basic_memory_buffer<Char, SIZE>& buf)
-> std::basic_string<Char> {
auto size = buf.size();
detail::assume(size < std::basic_string<Char>().max_size());
}
# endif
-/**
- \rst
- User-defined literal equivalent of :func:`fmt::format`.
-
- **Example**::
-
- using namespace fmt::literals;
- std::string message = "The answer is {}"_format(42);
- \endrst
- */
-constexpr auto operator"" _format(const char* s, size_t n)
+// DEPRECATED!
+// User-defined literal equivalent of fmt::format.
+FMT_DEPRECATED constexpr auto operator"" _format(const char* s, size_t n)
-> detail::udl_formatter<char> {
return {{s, n}};
}
#include "format.h"
+#ifndef FMT_USE_FCNTL
// UWP doesn't provide _pipe.
-#if FMT_HAS_INCLUDE("winapifamily.h")
-# include <winapifamily.h>
-#endif
-#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
- defined(__linux__)) && \
- (!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
-# include <fcntl.h> // for O_RDONLY
-# define FMT_USE_FCNTL 1
-#else
-# define FMT_USE_FCNTL 0
+# if FMT_HAS_INCLUDE("winapifamily.h")
+# include <winapifamily.h>
+# endif
+# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
+ defined(__linux__)) && \
+ (!defined(WINAPI_FAMILY) || \
+ (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
+# include <fcntl.h> // for O_RDONLY
+# define FMT_USE_FCNTL 1
+# else
+# define FMT_USE_FCNTL 0
+# endif
#endif
#ifndef FMT_POSIX
: ostream_params(params...) {
this->buffer_size = bs.value;
}
+
+// Intel has a bug that results in failure to deduce a constructor
+// for empty parameter packs.
+# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
+ ostream_params(int new_oflag) : oflag(new_oflag) {}
+ ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
+# endif
};
FMT_END_DETAIL_NAMESPACE
-constexpr detail::buffer_size buffer_size;
+// Added {} below to work around default constructor error known to
+// occur in Xcode versions 7.2.1 and 8.2.1.
+constexpr detail::buffer_size buffer_size{};
/** A fast output stream which is not thread-safe. */
class FMT_API ostream final : private detail::buffer<char> {
private:
file file_;
- void flush() {
- if (size() == 0) return;
- file_.write(data(), size());
- clear();
- }
-
void grow(size_t) override;
ostream(cstring_view path, const detail::ostream_params& params)
delete[] data();
}
+ void flush() {
+ if (size() == 0) return;
+ file_.write(data(), size());
+ clear();
+ }
+
template <typename... T>
friend ostream output_file(cstring_view path, T... params);
// Converts string to floating-point number and advances str past the end
// of the parsed input.
- double strtod(const char*& str) const {
+ FMT_DEPRECATED double strtod(const char*& str) const {
char* end = nullptr;
double result = strtod_l(str, &end, locale_);
str = end;
FMT_BEGIN_NAMESPACE
-template <typename Char> class basic_printf_parse_context;
template <typename OutputIt, typename Char> class basic_printf_context;
namespace detail {
-template <class Char> class formatbuf : public std::basic_streambuf<Char> {
- private:
- using int_type = typename std::basic_streambuf<Char>::int_type;
- using traits_type = typename std::basic_streambuf<Char>::traits_type;
-
- buffer<Char>& buffer_;
-
- public:
- formatbuf(buffer<Char>& buf) : buffer_(buf) {}
-
- protected:
- // The put-area is actually always empty. This makes the implementation
- // simpler and has the advantage that the streambuf and the buffer are always
- // in sync and sputc never writes into uninitialized memory. The obvious
- // disadvantage is that each call to sputc always results in a (virtual) call
- // to overflow. There is no disadvantage here for sputn since this always
- // results in a call to xsputn.
-
- int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
- if (!traits_type::eq_int_type(ch, traits_type::eof()))
- buffer_.push_back(static_cast<Char>(ch));
- return ch;
- }
-
- std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
- buffer_.append(s, s + count);
- return count;
- }
-};
-
-struct converter {
- template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
-};
-
-template <typename Char> struct test_stream : std::basic_ostream<Char> {
- private:
- void_t<> operator<<(converter);
-};
-
-// Hide insertion operators for built-in types.
-template <typename Char, typename Traits>
-void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
-template <typename Char, typename Traits>
-void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
-template <typename Traits>
-void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
-template <typename Traits>
-void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
-template <typename Traits>
-void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
-
-// Checks if T has a user-defined operator<< (e.g. not a member of
-// std::ostream).
-template <typename T, typename Char> class is_streamable {
+// Checks if T has a user-defined operator<<.
+template <typename T, typename Char, typename Enable = void>
+class is_streamable {
private:
template <typename U>
- static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
- << std::declval<U>()),
- void_t<>>::value>
- test(int);
+ static auto test(int)
+ -> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
+ << std::declval<U>()) != 0>;
- template <typename> static std::false_type test(...);
+ template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0));
static const bool value = result::value;
};
+// Formatting of built-in types and arrays is intentionally disabled because
+// it's handled by standard (non-ostream) formatters.
+template <typename T, typename Char>
+struct is_streamable<
+ T, Char,
+ enable_if_t<
+ std::is_arithmetic<T>::value || std::is_array<T>::value ||
+ std::is_pointer<T>::value || std::is_same<T, char8_type>::value ||
+ std::is_same<T, std::basic_string<Char>>::value ||
+ std::is_same<T, std_string_view<Char>>::value ||
+ (std::is_convertible<T, int>::value && !std::is_enum<T>::value)>>
+ : std::false_type {};
+
// Write the content of buf to os.
+// It is a separate function rather than a part of vprint to simplify testing.
template <typename Char>
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
const Char* buf_data = buf.data();
template <typename Char, typename T>
void format_value(buffer<Char>& buf, const T& value,
locale_ref loc = locale_ref()) {
- formatbuf<Char> format_buf(buf);
- std::basic_ostream<Char> output(&format_buf);
+ auto&& format_buf = formatbuf<std::basic_streambuf<Char>>(buf);
+ auto&& output = std::basic_ostream<Char>(&format_buf);
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
if (loc) output.imbue(loc.get<std::locale>());
#endif
template <typename T, typename Char>
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
: private formatter<basic_string_view<Char>, Char> {
- FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
- -> decltype(ctx.begin()) {
- return formatter<basic_string_view<Char>, Char>::parse(ctx);
- }
- template <typename ParseCtx,
- FMT_ENABLE_IF(std::is_same<
- ParseCtx, basic_printf_parse_context<Char>>::value)>
- auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
- return ctx.begin();
- }
+ using formatter<basic_string_view<Char>, Char>::parse;
template <typename OutputIt>
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
-> OutputIt {
- basic_memory_buffer<Char> buffer;
+ auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale());
- basic_string_view<Char> str(buffer.data(), buffer.size());
- return formatter<basic_string_view<Char>, Char>::format(str, ctx);
+ return formatter<basic_string_view<Char>, Char>::format(
+ {buffer.data(), buffer.size()}, ctx);
}
+
+ // DEPRECATED!
template <typename OutputIt>
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
-> OutputIt {
- basic_memory_buffer<Char> buffer;
+ auto buffer = basic_memory_buffer<Char>();
format_value(buffer, value, ctx.locale());
return std::copy(buffer.begin(), buffer.end(), ctx.out());
}
template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
- basic_memory_buffer<Char> buffer;
+ auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer);
}
OutputIt write_null_pointer(bool is_string = false) {
auto s = this->specs;
- s.type = 0;
+ s.type = presentation_type::none;
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
}
// std::is_same instead.
if (std::is_same<T, Char>::value) {
format_specs fmt_specs = this->specs;
- if (fmt_specs.type && fmt_specs.type != 'c')
+ if (fmt_specs.type != presentation_type::none &&
+ fmt_specs.type != presentation_type::chr) {
return (*this)(static_cast<int>(value));
+ }
fmt_specs.sign = sign::none;
fmt_specs.alt = false;
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
/** Formats a null-terminated C string. */
OutputIt operator()(const char* value) {
if (value) return base::operator()(value);
- return write_null_pointer(this->specs.type != 'p');
+ return write_null_pointer(this->specs.type != presentation_type::pointer);
}
/** Formats a null-terminated wide C string. */
OutputIt operator()(const wchar_t* value) {
if (value) return base::operator()(value);
- return write_null_pointer(this->specs.type != 'p');
+ return write_null_pointer(this->specs.type != presentation_type::pointer);
}
OutputIt operator()(basic_string_view<Char> value) {
// Parse type.
if (it == end) FMT_THROW(format_error("invalid format string"));
- specs.type = static_cast<char>(*it++);
+ char type = static_cast<char>(*it++);
if (arg.is_integral()) {
// Normalize type.
- switch (specs.type) {
+ switch (type) {
case 'i':
case 'u':
- specs.type = 'd';
+ type = 'd';
break;
case 'c':
visit_format_arg(
break;
}
}
+ specs.type = parse_presentation_type(type);
+ if (specs.type == presentation_type::none)
+ parse_ctx.on_error("invalid type specifier");
start = it;
#define FMT_RANGES_H_
#include <initializer_list>
+#include <tuple>
#include <type_traits>
#include "format.h"
FMT_BEGIN_NAMESPACE
-template <typename Char, typename Enable = void> struct formatting_range {
-#ifdef FMT_DEPRECATED_BRACED_RANGES
- Char prefix = '{';
- Char postfix = '}';
-#else
- Char prefix = '[';
- Char postfix = ']';
-#endif
-
- template <typename ParseContext>
- FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- return ctx.begin();
- }
-};
-
-template <typename Char, typename Enable = void> struct formatting_tuple {
- Char prefix = '(';
- Char postfix = ')';
-
- template <typename ParseContext>
- FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- return ctx.begin();
- }
-};
-
namespace detail {
template <typename RangeT, typename OutputIterator>
return out;
}
-/// Return true value if T has std::string interface, like std::string_view.
+// Returns true if T has a std::string-like interface, like std::string_view.
template <typename T> class is_std_string_like {
template <typename U>
static auto check(U* p)
public:
static FMT_CONSTEXPR_DECL const bool value =
- is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
+ is_string<T>::value ||
+ std::is_convertible<T, std_string_view<char>>::value ||
+ !std::is_void<decltype(check<T>(nullptr))>::value;
};
template <typename Char>
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
+template <typename T> class is_map {
+ template <typename U> static auto check(U*) -> typename U::mapped_type;
+ template <typename> static void check(...);
+
+ public:
+#ifdef FMT_FORMAT_MAP_AS_LIST
+ static FMT_CONSTEXPR_DECL const bool value = false;
+#else
+ static FMT_CONSTEXPR_DECL const bool value =
+ !std::is_void<decltype(check<T>(nullptr))>::value;
+#endif
+};
+
+template <typename T> class is_set {
+ template <typename U> static auto check(U*) -> typename U::key_type;
+ template <typename> static void check(...);
+
+ public:
+#ifdef FMT_FORMAT_SET_AS_LIST
+ static FMT_CONSTEXPR_DECL const bool value = false;
+#else
+ static FMT_CONSTEXPR_DECL const bool value =
+ !std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
+#endif
+};
+
template <typename... Ts> struct conditional_helper {};
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
template <typename T>
struct has_const_begin_end<
- T, void_t<decltype(detail::range_begin(
- std::declval<const remove_cvref_t<T>&>())),
- decltype(detail::range_begin(
- std::declval<const remove_cvref_t<T>&>()))>>
+ T,
+ void_t<
+ decltype(detail::range_begin(std::declval<const remove_cvref_t<T>&>())),
+ decltype(detail::range_end(std::declval<const remove_cvref_t<T>&>()))>>
: std::true_type {};
template <typename T>
struct has_mutable_begin_end<
T, void_t<decltype(detail::range_begin(std::declval<T>())),
- decltype(detail::range_begin(std::declval<T>())),
+ decltype(detail::range_end(std::declval<T>())),
enable_if_t<std::is_copy_constructible<T>::value>>>
: std::true_type {};
struct is_range_<T, void>
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
has_mutable_begin_end<T>::value)> {};
-
-template <typename T, typename Enable = void> struct range_to_view;
-template <typename T>
-struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
- struct view_t {
- const T* m_range_ptr;
-
- auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
- auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
- };
- static auto view(const T& range) -> view_t { return {&range}; }
-};
-
-template <typename T>
-struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
- has_mutable_begin_end<T>::value>> {
- struct view_t {
- T m_range_copy;
-
- auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
- auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
- };
- static auto view(const T& range) -> view_t { return {range}; }
-};
# undef FMT_DECLTYPE_RETURN
#endif
-/// tuple_size and tuple_element check.
+// tuple_size and tuple_element check.
template <typename T> class is_tuple_like_ {
template <typename U>
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
return out;
}
-template <
- typename Char, typename OutputIt, typename Arg,
- FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
-OutputIt write_range_entry(OutputIt out, const Arg& v) {
+struct singleton {
+ unsigned char upper;
+ unsigned char lower_count;
+};
+
+inline auto is_printable(uint16_t x, const singleton* singletons,
+ size_t singletons_size,
+ const unsigned char* singleton_lowers,
+ const unsigned char* normal, size_t normal_size)
+ -> bool {
+ auto upper = x >> 8;
+ auto lower_start = 0;
+ for (size_t i = 0; i < singletons_size; ++i) {
+ auto s = singletons[i];
+ auto lower_end = lower_start + s.lower_count;
+ if (upper < s.upper) break;
+ if (upper == s.upper) {
+ for (auto j = lower_start; j < lower_end; ++j) {
+ if (singleton_lowers[j] == (x & 0xff)) return false;
+ }
+ }
+ lower_start = lower_end;
+ }
+
+ auto xsigned = static_cast<int>(x);
+ auto current = true;
+ for (size_t i = 0; i < normal_size; ++i) {
+ auto v = static_cast<int>(normal[i]);
+ auto len = (v & 0x80) != 0 ? (v & 0x7f) << 8 | normal[++i] : v;
+ xsigned -= len;
+ if (xsigned < 0) break;
+ current = !current;
+ }
+ return current;
+}
+
+// Returns true iff the code point cp is printable.
+// This code is generated by support/printable.py.
+inline auto is_printable(uint32_t cp) -> bool {
+ static constexpr singleton singletons0[] = {
+ {0x00, 1}, {0x03, 5}, {0x05, 6}, {0x06, 3}, {0x07, 6}, {0x08, 8},
+ {0x09, 17}, {0x0a, 28}, {0x0b, 25}, {0x0c, 20}, {0x0d, 16}, {0x0e, 13},
+ {0x0f, 4}, {0x10, 3}, {0x12, 18}, {0x13, 9}, {0x16, 1}, {0x17, 5},
+ {0x18, 2}, {0x19, 3}, {0x1a, 7}, {0x1c, 2}, {0x1d, 1}, {0x1f, 22},
+ {0x20, 3}, {0x2b, 3}, {0x2c, 2}, {0x2d, 11}, {0x2e, 1}, {0x30, 3},
+ {0x31, 2}, {0x32, 1}, {0xa7, 2}, {0xa9, 2}, {0xaa, 4}, {0xab, 8},
+ {0xfa, 2}, {0xfb, 5}, {0xfd, 4}, {0xfe, 3}, {0xff, 9},
+ };
+ static constexpr unsigned char singletons0_lower[] = {
+ 0xad, 0x78, 0x79, 0x8b, 0x8d, 0xa2, 0x30, 0x57, 0x58, 0x8b, 0x8c, 0x90,
+ 0x1c, 0x1d, 0xdd, 0x0e, 0x0f, 0x4b, 0x4c, 0xfb, 0xfc, 0x2e, 0x2f, 0x3f,
+ 0x5c, 0x5d, 0x5f, 0xb5, 0xe2, 0x84, 0x8d, 0x8e, 0x91, 0x92, 0xa9, 0xb1,
+ 0xba, 0xbb, 0xc5, 0xc6, 0xc9, 0xca, 0xde, 0xe4, 0xe5, 0xff, 0x00, 0x04,
+ 0x11, 0x12, 0x29, 0x31, 0x34, 0x37, 0x3a, 0x3b, 0x3d, 0x49, 0x4a, 0x5d,
+ 0x84, 0x8e, 0x92, 0xa9, 0xb1, 0xb4, 0xba, 0xbb, 0xc6, 0xca, 0xce, 0xcf,
+ 0xe4, 0xe5, 0x00, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
+ 0x3b, 0x45, 0x46, 0x49, 0x4a, 0x5e, 0x64, 0x65, 0x84, 0x91, 0x9b, 0x9d,
+ 0xc9, 0xce, 0xcf, 0x0d, 0x11, 0x29, 0x45, 0x49, 0x57, 0x64, 0x65, 0x8d,
+ 0x91, 0xa9, 0xb4, 0xba, 0xbb, 0xc5, 0xc9, 0xdf, 0xe4, 0xe5, 0xf0, 0x0d,
+ 0x11, 0x45, 0x49, 0x64, 0x65, 0x80, 0x84, 0xb2, 0xbc, 0xbe, 0xbf, 0xd5,
+ 0xd7, 0xf0, 0xf1, 0x83, 0x85, 0x8b, 0xa4, 0xa6, 0xbe, 0xbf, 0xc5, 0xc7,
+ 0xce, 0xcf, 0xda, 0xdb, 0x48, 0x98, 0xbd, 0xcd, 0xc6, 0xce, 0xcf, 0x49,
+ 0x4e, 0x4f, 0x57, 0x59, 0x5e, 0x5f, 0x89, 0x8e, 0x8f, 0xb1, 0xb6, 0xb7,
+ 0xbf, 0xc1, 0xc6, 0xc7, 0xd7, 0x11, 0x16, 0x17, 0x5b, 0x5c, 0xf6, 0xf7,
+ 0xfe, 0xff, 0x80, 0x0d, 0x6d, 0x71, 0xde, 0xdf, 0x0e, 0x0f, 0x1f, 0x6e,
+ 0x6f, 0x1c, 0x1d, 0x5f, 0x7d, 0x7e, 0xae, 0xaf, 0xbb, 0xbc, 0xfa, 0x16,
+ 0x17, 0x1e, 0x1f, 0x46, 0x47, 0x4e, 0x4f, 0x58, 0x5a, 0x5c, 0x5e, 0x7e,
+ 0x7f, 0xb5, 0xc5, 0xd4, 0xd5, 0xdc, 0xf0, 0xf1, 0xf5, 0x72, 0x73, 0x8f,
+ 0x74, 0x75, 0x96, 0x2f, 0x5f, 0x26, 0x2e, 0x2f, 0xa7, 0xaf, 0xb7, 0xbf,
+ 0xc7, 0xcf, 0xd7, 0xdf, 0x9a, 0x40, 0x97, 0x98, 0x30, 0x8f, 0x1f, 0xc0,
+ 0xc1, 0xce, 0xff, 0x4e, 0x4f, 0x5a, 0x5b, 0x07, 0x08, 0x0f, 0x10, 0x27,
+ 0x2f, 0xee, 0xef, 0x6e, 0x6f, 0x37, 0x3d, 0x3f, 0x42, 0x45, 0x90, 0x91,
+ 0xfe, 0xff, 0x53, 0x67, 0x75, 0xc8, 0xc9, 0xd0, 0xd1, 0xd8, 0xd9, 0xe7,
+ 0xfe, 0xff,
+ };
+ static constexpr singleton singletons1[] = {
+ {0x00, 6}, {0x01, 1}, {0x03, 1}, {0x04, 2}, {0x08, 8}, {0x09, 2},
+ {0x0a, 5}, {0x0b, 2}, {0x0e, 4}, {0x10, 1}, {0x11, 2}, {0x12, 5},
+ {0x13, 17}, {0x14, 1}, {0x15, 2}, {0x17, 2}, {0x19, 13}, {0x1c, 5},
+ {0x1d, 8}, {0x24, 1}, {0x6a, 3}, {0x6b, 2}, {0xbc, 2}, {0xd1, 2},
+ {0xd4, 12}, {0xd5, 9}, {0xd6, 2}, {0xd7, 2}, {0xda, 1}, {0xe0, 5},
+ {0xe1, 2}, {0xe8, 2}, {0xee, 32}, {0xf0, 4}, {0xf8, 2}, {0xf9, 2},
+ {0xfa, 2}, {0xfb, 1},
+ };
+ static constexpr unsigned char singletons1_lower[] = {
+ 0x0c, 0x27, 0x3b, 0x3e, 0x4e, 0x4f, 0x8f, 0x9e, 0x9e, 0x9f, 0x06, 0x07,
+ 0x09, 0x36, 0x3d, 0x3e, 0x56, 0xf3, 0xd0, 0xd1, 0x04, 0x14, 0x18, 0x36,
+ 0x37, 0x56, 0x57, 0x7f, 0xaa, 0xae, 0xaf, 0xbd, 0x35, 0xe0, 0x12, 0x87,
+ 0x89, 0x8e, 0x9e, 0x04, 0x0d, 0x0e, 0x11, 0x12, 0x29, 0x31, 0x34, 0x3a,
+ 0x45, 0x46, 0x49, 0x4a, 0x4e, 0x4f, 0x64, 0x65, 0x5c, 0xb6, 0xb7, 0x1b,
+ 0x1c, 0x07, 0x08, 0x0a, 0x0b, 0x14, 0x17, 0x36, 0x39, 0x3a, 0xa8, 0xa9,
+ 0xd8, 0xd9, 0x09, 0x37, 0x90, 0x91, 0xa8, 0x07, 0x0a, 0x3b, 0x3e, 0x66,
+ 0x69, 0x8f, 0x92, 0x6f, 0x5f, 0xee, 0xef, 0x5a, 0x62, 0x9a, 0x9b, 0x27,
+ 0x28, 0x55, 0x9d, 0xa0, 0xa1, 0xa3, 0xa4, 0xa7, 0xa8, 0xad, 0xba, 0xbc,
+ 0xc4, 0x06, 0x0b, 0x0c, 0x15, 0x1d, 0x3a, 0x3f, 0x45, 0x51, 0xa6, 0xa7,
+ 0xcc, 0xcd, 0xa0, 0x07, 0x19, 0x1a, 0x22, 0x25, 0x3e, 0x3f, 0xc5, 0xc6,
+ 0x04, 0x20, 0x23, 0x25, 0x26, 0x28, 0x33, 0x38, 0x3a, 0x48, 0x4a, 0x4c,
+ 0x50, 0x53, 0x55, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 0x60, 0x63, 0x65, 0x66,
+ 0x6b, 0x73, 0x78, 0x7d, 0x7f, 0x8a, 0xa4, 0xaa, 0xaf, 0xb0, 0xc0, 0xd0,
+ 0xae, 0xaf, 0x79, 0xcc, 0x6e, 0x6f, 0x93,
+ };
+ static constexpr unsigned char normal0[] = {
+ 0x00, 0x20, 0x5f, 0x22, 0x82, 0xdf, 0x04, 0x82, 0x44, 0x08, 0x1b, 0x04,
+ 0x06, 0x11, 0x81, 0xac, 0x0e, 0x80, 0xab, 0x35, 0x28, 0x0b, 0x80, 0xe0,
+ 0x03, 0x19, 0x08, 0x01, 0x04, 0x2f, 0x04, 0x34, 0x04, 0x07, 0x03, 0x01,
+ 0x07, 0x06, 0x07, 0x11, 0x0a, 0x50, 0x0f, 0x12, 0x07, 0x55, 0x07, 0x03,
+ 0x04, 0x1c, 0x0a, 0x09, 0x03, 0x08, 0x03, 0x07, 0x03, 0x02, 0x03, 0x03,
+ 0x03, 0x0c, 0x04, 0x05, 0x03, 0x0b, 0x06, 0x01, 0x0e, 0x15, 0x05, 0x3a,
+ 0x03, 0x11, 0x07, 0x06, 0x05, 0x10, 0x07, 0x57, 0x07, 0x02, 0x07, 0x15,
+ 0x0d, 0x50, 0x04, 0x43, 0x03, 0x2d, 0x03, 0x01, 0x04, 0x11, 0x06, 0x0f,
+ 0x0c, 0x3a, 0x04, 0x1d, 0x25, 0x5f, 0x20, 0x6d, 0x04, 0x6a, 0x25, 0x80,
+ 0xc8, 0x05, 0x82, 0xb0, 0x03, 0x1a, 0x06, 0x82, 0xfd, 0x03, 0x59, 0x07,
+ 0x15, 0x0b, 0x17, 0x09, 0x14, 0x0c, 0x14, 0x0c, 0x6a, 0x06, 0x0a, 0x06,
+ 0x1a, 0x06, 0x59, 0x07, 0x2b, 0x05, 0x46, 0x0a, 0x2c, 0x04, 0x0c, 0x04,
+ 0x01, 0x03, 0x31, 0x0b, 0x2c, 0x04, 0x1a, 0x06, 0x0b, 0x03, 0x80, 0xac,
+ 0x06, 0x0a, 0x06, 0x21, 0x3f, 0x4c, 0x04, 0x2d, 0x03, 0x74, 0x08, 0x3c,
+ 0x03, 0x0f, 0x03, 0x3c, 0x07, 0x38, 0x08, 0x2b, 0x05, 0x82, 0xff, 0x11,
+ 0x18, 0x08, 0x2f, 0x11, 0x2d, 0x03, 0x20, 0x10, 0x21, 0x0f, 0x80, 0x8c,
+ 0x04, 0x82, 0x97, 0x19, 0x0b, 0x15, 0x88, 0x94, 0x05, 0x2f, 0x05, 0x3b,
+ 0x07, 0x02, 0x0e, 0x18, 0x09, 0x80, 0xb3, 0x2d, 0x74, 0x0c, 0x80, 0xd6,
+ 0x1a, 0x0c, 0x05, 0x80, 0xff, 0x05, 0x80, 0xdf, 0x0c, 0xee, 0x0d, 0x03,
+ 0x84, 0x8d, 0x03, 0x37, 0x09, 0x81, 0x5c, 0x14, 0x80, 0xb8, 0x08, 0x80,
+ 0xcb, 0x2a, 0x38, 0x03, 0x0a, 0x06, 0x38, 0x08, 0x46, 0x08, 0x0c, 0x06,
+ 0x74, 0x0b, 0x1e, 0x03, 0x5a, 0x04, 0x59, 0x09, 0x80, 0x83, 0x18, 0x1c,
+ 0x0a, 0x16, 0x09, 0x4c, 0x04, 0x80, 0x8a, 0x06, 0xab, 0xa4, 0x0c, 0x17,
+ 0x04, 0x31, 0xa1, 0x04, 0x81, 0xda, 0x26, 0x07, 0x0c, 0x05, 0x05, 0x80,
+ 0xa5, 0x11, 0x81, 0x6d, 0x10, 0x78, 0x28, 0x2a, 0x06, 0x4c, 0x04, 0x80,
+ 0x8d, 0x04, 0x80, 0xbe, 0x03, 0x1b, 0x03, 0x0f, 0x0d,
+ };
+ static constexpr unsigned char normal1[] = {
+ 0x5e, 0x22, 0x7b, 0x05, 0x03, 0x04, 0x2d, 0x03, 0x66, 0x03, 0x01, 0x2f,
+ 0x2e, 0x80, 0x82, 0x1d, 0x03, 0x31, 0x0f, 0x1c, 0x04, 0x24, 0x09, 0x1e,
+ 0x05, 0x2b, 0x05, 0x44, 0x04, 0x0e, 0x2a, 0x80, 0xaa, 0x06, 0x24, 0x04,
+ 0x24, 0x04, 0x28, 0x08, 0x34, 0x0b, 0x01, 0x80, 0x90, 0x81, 0x37, 0x09,
+ 0x16, 0x0a, 0x08, 0x80, 0x98, 0x39, 0x03, 0x63, 0x08, 0x09, 0x30, 0x16,
+ 0x05, 0x21, 0x03, 0x1b, 0x05, 0x01, 0x40, 0x38, 0x04, 0x4b, 0x05, 0x2f,
+ 0x04, 0x0a, 0x07, 0x09, 0x07, 0x40, 0x20, 0x27, 0x04, 0x0c, 0x09, 0x36,
+ 0x03, 0x3a, 0x05, 0x1a, 0x07, 0x04, 0x0c, 0x07, 0x50, 0x49, 0x37, 0x33,
+ 0x0d, 0x33, 0x07, 0x2e, 0x08, 0x0a, 0x81, 0x26, 0x52, 0x4e, 0x28, 0x08,
+ 0x2a, 0x56, 0x1c, 0x14, 0x17, 0x09, 0x4e, 0x04, 0x1e, 0x0f, 0x43, 0x0e,
+ 0x19, 0x07, 0x0a, 0x06, 0x48, 0x08, 0x27, 0x09, 0x75, 0x0b, 0x3f, 0x41,
+ 0x2a, 0x06, 0x3b, 0x05, 0x0a, 0x06, 0x51, 0x06, 0x01, 0x05, 0x10, 0x03,
+ 0x05, 0x80, 0x8b, 0x62, 0x1e, 0x48, 0x08, 0x0a, 0x80, 0xa6, 0x5e, 0x22,
+ 0x45, 0x0b, 0x0a, 0x06, 0x0d, 0x13, 0x39, 0x07, 0x0a, 0x36, 0x2c, 0x04,
+ 0x10, 0x80, 0xc0, 0x3c, 0x64, 0x53, 0x0c, 0x48, 0x09, 0x0a, 0x46, 0x45,
+ 0x1b, 0x48, 0x08, 0x53, 0x1d, 0x39, 0x81, 0x07, 0x46, 0x0a, 0x1d, 0x03,
+ 0x47, 0x49, 0x37, 0x03, 0x0e, 0x08, 0x0a, 0x06, 0x39, 0x07, 0x0a, 0x81,
+ 0x36, 0x19, 0x80, 0xb7, 0x01, 0x0f, 0x32, 0x0d, 0x83, 0x9b, 0x66, 0x75,
+ 0x0b, 0x80, 0xc4, 0x8a, 0xbc, 0x84, 0x2f, 0x8f, 0xd1, 0x82, 0x47, 0xa1,
+ 0xb9, 0x82, 0x39, 0x07, 0x2a, 0x04, 0x02, 0x60, 0x26, 0x0a, 0x46, 0x0a,
+ 0x28, 0x05, 0x13, 0x82, 0xb0, 0x5b, 0x65, 0x4b, 0x04, 0x39, 0x07, 0x11,
+ 0x40, 0x05, 0x0b, 0x02, 0x0e, 0x97, 0xf8, 0x08, 0x84, 0xd6, 0x2a, 0x09,
+ 0xa2, 0xf7, 0x81, 0x1f, 0x31, 0x03, 0x11, 0x04, 0x08, 0x81, 0x8c, 0x89,
+ 0x04, 0x6b, 0x05, 0x0d, 0x03, 0x09, 0x07, 0x10, 0x93, 0x60, 0x80, 0xf6,
+ 0x0a, 0x73, 0x08, 0x6e, 0x17, 0x46, 0x80, 0x9a, 0x14, 0x0c, 0x57, 0x09,
+ 0x19, 0x80, 0x87, 0x81, 0x47, 0x03, 0x85, 0x42, 0x0f, 0x15, 0x85, 0x50,
+ 0x2b, 0x80, 0xd5, 0x2d, 0x03, 0x1a, 0x04, 0x02, 0x81, 0x70, 0x3a, 0x05,
+ 0x01, 0x85, 0x00, 0x80, 0xd7, 0x29, 0x4c, 0x04, 0x0a, 0x04, 0x02, 0x83,
+ 0x11, 0x44, 0x4c, 0x3d, 0x80, 0xc2, 0x3c, 0x06, 0x01, 0x04, 0x55, 0x05,
+ 0x1b, 0x34, 0x02, 0x81, 0x0e, 0x2c, 0x04, 0x64, 0x0c, 0x56, 0x0a, 0x80,
+ 0xae, 0x38, 0x1d, 0x0d, 0x2c, 0x04, 0x09, 0x07, 0x02, 0x0e, 0x06, 0x80,
+ 0x9a, 0x83, 0xd8, 0x08, 0x0d, 0x03, 0x0d, 0x03, 0x74, 0x0c, 0x59, 0x07,
+ 0x0c, 0x14, 0x0c, 0x04, 0x38, 0x08, 0x0a, 0x06, 0x28, 0x08, 0x22, 0x4e,
+ 0x81, 0x54, 0x0c, 0x15, 0x03, 0x03, 0x05, 0x07, 0x09, 0x19, 0x07, 0x07,
+ 0x09, 0x03, 0x0d, 0x07, 0x29, 0x80, 0xcb, 0x25, 0x0a, 0x84, 0x06,
+ };
+ auto lower = static_cast<uint16_t>(cp);
+ if (cp < 0x10000) {
+ return is_printable(lower, singletons0,
+ sizeof(singletons0) / sizeof(*singletons0),
+ singletons0_lower, normal0, sizeof(normal0));
+ }
+ if (cp < 0x20000) {
+ return is_printable(lower, singletons1,
+ sizeof(singletons1) / sizeof(*singletons1),
+ singletons1_lower, normal1, sizeof(normal1));
+ }
+ if (0x2a6de <= cp && cp < 0x2a700) return false;
+ if (0x2b735 <= cp && cp < 0x2b740) return false;
+ if (0x2b81e <= cp && cp < 0x2b820) return false;
+ if (0x2cea2 <= cp && cp < 0x2ceb0) return false;
+ if (0x2ebe1 <= cp && cp < 0x2f800) return false;
+ if (0x2fa1e <= cp && cp < 0x30000) return false;
+ if (0x3134b <= cp && cp < 0xe0100) return false;
+ if (0xe01f0 <= cp && cp < 0x110000) return false;
+ return cp < 0x110000;
+}
+
+inline auto needs_escape(uint32_t cp) -> bool {
+ return cp < 0x20 || cp == 0x7f || cp == '"' || cp == '\\' ||
+ !is_printable(cp);
+}
+
+template <typename Char> struct find_escape_result {
+ const Char* begin;
+ const Char* end;
+ uint32_t cp;
+};
+
+template <typename Char>
+auto find_escape(const Char* begin, const Char* end)
+ -> find_escape_result<Char> {
+ for (; begin != end; ++begin) {
+ auto cp = static_cast<typename std::make_unsigned<Char>::type>(*begin);
+ if (sizeof(Char) == 1 && cp >= 0x80) continue;
+ if (needs_escape(cp)) return {begin, begin + 1, cp};
+ }
+ return {begin, nullptr, 0};
+}
+
+inline auto find_escape(const char* begin, const char* end)
+ -> find_escape_result<char> {
+ if (!is_utf8()) return find_escape<char>(begin, end);
+ auto result = find_escape_result<char>{end, nullptr, 0};
+ for_each_codepoint(string_view(begin, to_unsigned(end - begin)),
+ [&](uint32_t cp, string_view sv) {
+ if (needs_escape(cp)) {
+ result = {sv.begin(), sv.end(), cp};
+ return false;
+ }
+ return true;
+ });
+ return result;
+}
+
+template <typename Char, typename OutputIt>
+auto write_range_entry(OutputIt out, basic_string_view<Char> str) -> OutputIt {
*out++ = '"';
- out = write<Char>(out, v);
+ auto begin = str.begin(), end = str.end();
+ do {
+ auto escape = find_escape(begin, end);
+ out = copy_str<Char>(begin, escape.begin, out);
+ begin = escape.end;
+ if (!begin) break;
+ auto c = static_cast<Char>(escape.cp);
+ switch (escape.cp) {
+ case '\n':
+ *out++ = '\\';
+ c = 'n';
+ break;
+ case '\r':
+ *out++ = '\\';
+ c = 'r';
+ break;
+ case '\t':
+ *out++ = '\\';
+ c = 't';
+ break;
+ case '"':
+ FMT_FALLTHROUGH;
+ case '\\':
+ *out++ = '\\';
+ break;
+ default:
+ if (is_utf8()) {
+ if (escape.cp < 0x100) {
+ out = format_to(out, "\\x{:02x}", escape.cp);
+ continue;
+ }
+ if (escape.cp < 0x10000) {
+ out = format_to(out, "\\u{:04x}", escape.cp);
+ continue;
+ }
+ if (escape.cp < 0x110000) {
+ out = format_to(out, "\\U{:08x}", escape.cp);
+ continue;
+ }
+ }
+ for (Char escape_char : basic_string_view<Char>(
+ escape.begin, to_unsigned(escape.end - escape.begin))) {
+ out = format_to(
+ out, "\\x{:02x}",
+ static_cast<typename std::make_unsigned<Char>::type>(escape_char));
+ }
+ continue;
+ }
+ *out++ = c;
+ } while (begin != end);
*out++ = '"';
return out;
}
+template <typename Char, typename OutputIt, typename T,
+ FMT_ENABLE_IF(std::is_convertible<T, std_string_view<char>>::value)>
+inline auto write_range_entry(OutputIt out, const T& str) -> OutputIt {
+ auto sv = std_string_view<Char>(str);
+ return write_range_entry<Char>(out, basic_string_view<Char>(sv));
+}
+
template <typename Char, typename OutputIt, typename Arg,
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
OutputIt write_range_entry(OutputIt out, const Arg v) {
template <typename TupleT, typename Char>
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
private:
- // C++11 generic lambda for format()
+ // C++11 generic lambda for format().
template <typename FormatContext> struct format_each {
template <typename T> void operator()(const T& v) {
if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, v);
++i;
}
- formatting_tuple<Char>& formatting;
- size_t& i;
- typename std::add_lvalue_reference<
- decltype(std::declval<FormatContext>().out())>::type out;
+ int i;
+ typename FormatContext::iterator& out;
};
public:
- formatting_tuple<Char> formatting;
-
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- return formatting.parse(ctx);
+ return ctx.begin();
}
template <typename FormatContext = format_context>
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
auto out = ctx.out();
- size_t i = 0;
-
- detail::copy(formatting.prefix, out);
- detail::for_each(values, format_each<FormatContext>{formatting, i, out});
- detail::copy(formatting.postfix, out);
-
- return ctx.out();
+ *out++ = '(';
+ detail::for_each(values, format_each<FormatContext>{0, out});
+ *out++ = ')';
+ return out;
}
};
template <typename T, typename Char> struct is_range {
static FMT_CONSTEXPR_DECL const bool value =
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
+ !detail::is_map<T>::value &&
!std::is_convertible<T, std::basic_string<Char>>::value &&
!std::is_constructible<detail::std_string_view<Char>, T>::value;
};
T, Char,
enable_if_t<
fmt::is_range<T, Char>::value
-// Workaround a bug in MSVC 2017 and earlier.
-#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
- && (has_formatter<detail::value_type<T>, format_context>::value ||
+// Workaround a bug in MSVC 2019 and earlier.
+#if !FMT_MSC_VER
+ && (is_formattable<detail::value_type<T>, Char>::value ||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
#endif
>> {
- formatting_range<Char> formatting;
-
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- return formatting.parse(ctx);
+ return ctx.begin();
}
- template <typename FormatContext>
- typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
- auto out = detail::copy(formatting.prefix, ctx.out());
- size_t i = 0;
- auto view = detail::range_to_view<T>::view(values);
- auto it = view.begin();
- auto end = view.end();
+ template <
+ typename FormatContext, typename U,
+ FMT_ENABLE_IF(
+ std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
+ const T, T>>::value)>
+ auto format(U& range, FormatContext& ctx) -> decltype(ctx.out()) {
+#ifdef FMT_DEPRECATED_BRACED_RANGES
+ Char prefix = '{';
+ Char postfix = '}';
+#else
+ Char prefix = detail::is_set<T>::value ? '{' : '[';
+ Char postfix = detail::is_set<T>::value ? '}' : ']';
+#endif
+ auto out = ctx.out();
+ *out++ = prefix;
+ int i = 0;
+ auto it = std::begin(range);
+ auto end = std::end(range);
for (; it != end; ++it) {
if (i > 0) out = detail::write_delimiter(out);
out = detail::write_range_entry<Char>(out, *it);
++i;
}
- return detail::copy(formatting.postfix, out);
+ *out++ = postfix;
+ return out;
+ }
+};
+
+template <typename T, typename Char>
+struct formatter<
+ T, Char,
+ enable_if_t<
+ detail::is_map<T>::value
+// Workaround a bug in MSVC 2019 and earlier.
+#if !FMT_MSC_VER
+ && (is_formattable<detail::value_type<T>, Char>::value ||
+ detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
+#endif
+ >> {
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
+ return ctx.begin();
+ }
+
+ template <
+ typename FormatContext, typename U,
+ FMT_ENABLE_IF(
+ std::is_same<U, conditional_t<detail::has_const_begin_end<T>::value,
+ const T, T>>::value)>
+ auto format(U& map, FormatContext& ctx) -> decltype(ctx.out()) {
+ auto out = ctx.out();
+ *out++ = '{';
+ int i = 0;
+ for (const auto& item : map) {
+ if (i > 0) out = detail::write_delimiter(out);
+ out = detail::write_range_entry<Char>(out, item.first);
+ *out++ = ':';
+ *out++ = ' ';
+ out = detail::write_range_entry<Char>(out, item.second);
+ ++i;
+ }
+ *out++ = '}';
+ return out;
}
};
template <typename Char, typename... T>
using tuple_arg_join = tuple_join_view<Char, T...>;
+// Define FMT_TUPLE_JOIN_SPECIFIERS to enable experimental format specifiers
+// support in tuple_join. It is disabled by default because of issues with
+// the dynamic width and precision.
+#ifndef FMT_TUPLE_JOIN_SPECIFIERS
+# define FMT_TUPLE_JOIN_SPECIFIERS 0
+#endif
+
template <typename Char, typename... T>
struct formatter<tuple_join_view<Char, T...>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
- return ctx.begin();
+ return do_parse(ctx, std::integral_constant<size_t, sizeof...(T)>());
}
template <typename FormatContext>
- auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
- typename FormatContext::iterator {
- return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
+ auto format(const tuple_join_view<Char, T...>& value,
+ FormatContext& ctx) const -> typename FormatContext::iterator {
+ return do_format(value, ctx,
+ std::integral_constant<size_t, sizeof...(T)>());
}
private:
- template <typename FormatContext, size_t... N>
- auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
- detail::index_sequence<N...>) ->
- typename FormatContext::iterator {
- using std::get;
- return format_args(value, ctx, get<N>(value.tuple)...);
+ std::tuple<formatter<typename std::decay<T>::type, Char>...> formatters_;
+
+ template <typename ParseContext>
+ FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
+ std::integral_constant<size_t, 0>)
+ -> decltype(ctx.begin()) {
+ return ctx.begin();
+ }
+
+ template <typename ParseContext, size_t N>
+ FMT_CONSTEXPR auto do_parse(ParseContext& ctx,
+ std::integral_constant<size_t, N>)
+ -> decltype(ctx.begin()) {
+ auto end = ctx.begin();
+#if FMT_TUPLE_JOIN_SPECIFIERS
+ end = std::get<sizeof...(T) - N>(formatters_).parse(ctx);
+ if (N > 1) {
+ auto end1 = do_parse(ctx, std::integral_constant<size_t, N - 1>());
+ if (end != end1)
+ FMT_THROW(format_error("incompatible format specs for tuple elements"));
+ }
+#endif
+ return end;
}
template <typename FormatContext>
- auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
+ auto do_format(const tuple_join_view<Char, T...>&, FormatContext& ctx,
+ std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
- // NOTE: for compilers that support C++17, this empty function instantiation
- // can be replaced with a constexpr branch in the variadic overload.
return ctx.out();
}
- template <typename FormatContext, typename Arg, typename... Args>
- auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
- const Arg& arg, const Args&... args) ->
+ template <typename FormatContext, size_t N>
+ auto do_format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
+ std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
- using base = formatter<typename std::decay<Arg>::type, Char>;
- auto out = base().format(arg, ctx);
- if (sizeof...(Args) > 0) {
+ auto out = std::get<sizeof...(T) - N>(formatters_)
+ .format(std::get<sizeof...(T) - N>(value.tuple), ctx);
+ if (N > 1) {
out = std::copy(value.sep.begin(), value.sep.end(), out);
ctx.advance_to(out);
- return format_args(value, ctx, args...);
+ return do_format(value, ctx, std::integral_constant<size_t, N - 1>());
}
return out;
}
FMT_BEGIN_NAMESPACE
namespace detail {
+// DEPRECATED!
+template <typename T = void> struct basic_data {
+ FMT_API static constexpr const char digits[100][2] = {
+ {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'},
+ {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'},
+ {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'},
+ {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'},
+ {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'},
+ {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'},
+ {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'},
+ {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'},
+ {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'},
+ {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'},
+ {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'},
+ {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'},
+ {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'},
+ {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'},
+ {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'},
+ {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'},
+ {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}};
+ FMT_API static constexpr const char hex_digits[] = "0123456789abcdef";
+ FMT_API static constexpr const char signs[4] = {0, '-', '+', ' '};
+ FMT_API static constexpr const char left_padding_shifts[5] = {31, 31, 0, 1,
+ 0};
+ FMT_API static constexpr const char right_padding_shifts[5] = {0, 31, 0, 1,
+ 0};
+ FMT_API static constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
+ 0x1000000u | ' '};
+};
+
+#ifdef FMT_SHARED
+// Required for -flto, -fivisibility=hidden and -shared to work
+extern template struct basic_data<void>;
+#endif
+
+#if __cplusplus < 201703L
+// DEPRECATED! These are here only for ABI compatiblity.
+template <typename T> constexpr const char basic_data<T>::digits[][2];
+template <typename T> constexpr const char basic_data<T>::hex_digits[];
+template <typename T> constexpr const char basic_data<T>::signs[];
+template <typename T> constexpr const char basic_data<T>::left_padding_shifts[];
+template <typename T>
+constexpr const char basic_data<T>::right_padding_shifts[];
+template <typename T> constexpr const unsigned basic_data<T>::prefixes[];
+#endif
+
template <typename T>
int format_float(char* buf, std::size_t size, const char* format, int precision,
T value) {
# endif
# include <io.h>
-# define O_CREAT _O_CREAT
-# define O_TRUNC _O_TRUNC
-
# ifndef S_IRUSR
# define S_IRUSR _S_IREAD
# endif
-
# ifndef S_IWUSR
# define S_IWUSR _S_IWRITE
# endif
-
-# ifdef __MINGW32__
-# define _SH_DENYNO 0x40
+# ifndef S_IRGRP
+# define S_IRGRP 0
+# endif
+# ifndef S_IROTH
+# define S_IROTH 0
# endif
# endif // _WIN32
#endif // FMT_USE_FCNTL
#if FMT_USE_FCNTL
file::file(cstring_view path, int oflag) {
- int mode = S_IRUSR | S_IWUSR;
+# ifdef _WIN32
+ using mode_t = int;
+# endif
+ mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
# if defined(_WIN32) && !defined(__MINGW32__)
fd_ = -1;
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));