Compare commits

...

45 Commits

Author SHA1 Message Date
353bd895a2 Add FMT_EXPORT on ranges.h customization points (#4476) 2025-06-24 10:30:44 -07:00
953cffa701 Replace memset with constexpr fill_n in bigint::align (#4471)
Use fill_n in place of memset in bigint::align to respect constexpr.
2025-06-23 16:18:16 -07:00
571c02d475 Add xchar support for std::byte formatter (#4480)
Signed-off-by: Vladislav Shchapov <vladislav@shchapov.ru>
2025-06-23 14:02:11 -07:00
f4345467fc Fix compilation on clang-21 / libc++-21 (#4477)
`<cstdlib>` was not being included, so malloc and free were only declared
via transitive includes. Some includes changed in the latest libc++-21
build which broke fmt.

Also changed `malloc`/`free` to `std::malloc` and `std::free`, as
putting those symbols in the global namespace is optional for the
implementation when including `<cstdlib>`.
2025-06-21 07:28:14 -07:00
1ef8348070 Properly constrain detail::copy optimization (#4474) 2025-06-21 06:59:35 -07:00
a5dccffa56 Add double and float support to scan test
- Add double_type and float_type to scan_type enum
- Add double* and float* pointers to scan_arg union
- Add constructors for double and float scan arguments
- Add switch cases for double and float types in visit()
- Implement basic read() functions for floating-point parsing

This partially resolves the TODO comment 'more types' in scan.h by adding
support for the two most commonly needed floating-point types.
2025-06-21 06:57:20 -07:00
4a149f513f Test non-SSO constexpr string formatting 2025-06-20 07:10:12 -07:00
067bc479b4 Avoid redundant work when processing UTF-8 strings (#4475) 2025-06-20 06:39:06 -07:00
730fd4d9a7 Remove redundant tests 2025-06-08 08:46:22 -07:00
5860688d7e Enable constexpr support for fmt::format (fmtlib#3403) (#4456) 2025-06-07 07:16:49 -07:00
46be88bc1e Cleanup FP formatting 2025-06-01 09:15:20 -07:00
cc88914904 Export fmt::dynamic_format_arg_store in fmt module (#4459) 2025-06-01 08:50:14 -07:00
fc0c76a075 Handle large precision 2025-06-01 08:49:41 -07:00
6332a38529 Bump ossf/scorecard-action from 2.4.0 to 2.4.2 (#4462)
Bumps [ossf/scorecard-action](https://github.com/ossf/scorecard-action) from 2.4.0 to 2.4.2.
- [Release notes](https://github.com/ossf/scorecard-action/releases)
- [Changelog](https://github.com/ossf/scorecard-action/blob/main/RELEASE.md)
- [Commits](62b2cac7ed...05b42c6244)

---
updated-dependencies:
- dependency-name: ossf/scorecard-action
  dependency-version: 2.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-01 08:48:36 -07:00
02de29e003 Remove a reference to a compromised account 2025-05-30 18:24:56 -07:00
6d51c78c1e Cleanup FP formatting 2025-05-30 16:45:21 -07:00
0f4e9d0bde Cleanup FP formatting 2025-05-30 16:05:57 -07:00
d9d50495ac Optimize the default FP formatting 2025-05-30 13:45:04 -07:00
befbc5fdb8 Fix ADL lookup for memory_buffer 2025-05-26 09:44:35 -07:00
8aa1d6a9fb Minor cleanup 2025-05-25 10:14:24 -07:00
6d79757a38 Interpret precision as display width (#4443) 2025-05-25 08:42:47 -07:00
1ff0b7f5e1 Cleanup warning suppression 2025-05-24 09:37:01 -07:00
ea985e84f8 Remove some implicit conversions (#4447)
* fix: avoid an implicit cast

The "1" used for the bitshift is treated as int, and this causes an
implicit conversion to `UInt` when performing the logical and.
Explicitly casting the number to `UInt` avoids the warning.

* fix: avoid implicit conversions for indices

Some indices in `include/fmt/base.h` are expressed as `int` types, which
causes an implicit conversion to a `size_t` when they are actually used
as index. Explicitly casting the value avoids the warning.

* fix: avoid an implicit conversion using size_t

The number of bits is used to express the size of a buffer. Using an
`int` causes an implicit conversion warning, let's use a `size_t` which
is the right type for the job.
2025-05-24 09:22:03 -07:00
f7033da09e Avoid include locale inline if C++20 modules are enabled (#4451)
MSVC hints with:
```
fmt\include\fmt\format-inl.h(26): warning C5244: '#include <locale>' in the purview of module 'fmt' appears erroneous.  Consider moving that directive before the module declaration, or replace the textual inclusion with 'import <locale>;'.
```

Then fails the build with `type redefinition`.
2025-05-21 17:18:18 -07:00
b723c021df Give useful error when misusing fmt::ptr. (#4453)
static_assert(bla, "") prints an empty message but not the condition with at least MSVC. Add an informative message.
2025-05-20 12:21:06 -07:00
3ba3c390fb Clarify that formatting of pointers is disallowed 2025-05-17 10:16:58 -07:00
ab161a71c6 Fix some typos in comments (#4448)
- s/instantion/instantiation/
- s/uninitalized/uninitialized/
- s/costexpr/constexpr/

Signed-off-by: Kefu Chai <tchaikov@gmail.com>
2025-05-15 06:28:14 -07:00
b5266fd3b9 Remove some redundant consts (#4445)
`constexpr` variables are implicitly `const`.
2025-05-12 10:41:58 -07:00
9b0ebd4435 Cleanup base-test 2025-05-11 15:42:08 -07:00
7af94e5597 Remove old gcc workaround 2025-05-11 12:35:28 -07:00
2924fcf8f6 Cleanup base-test 2025-05-11 10:36:42 -07:00
102752ad45 Update docs 2025-05-11 09:13:12 -07:00
a6cd72c9eb Cleanup base-test 2025-05-11 09:01:28 -07:00
07885271a1 Minor cleanup 2025-05-11 07:54:23 -07:00
4999416e5c Fix reference_wrapper ambiguity with format_as (#4434) 2025-05-10 11:15:45 -07:00
55a8f6a4be Change component prefix for NSIS compatibility (#4442) 2025-05-09 12:09:18 -07:00
eb9a95d426 Clarify that formatting of pointers is disallowed 2025-05-05 10:55:59 -07:00
d5c33e4f45 Make template parameter order consistent 2025-05-04 15:23:48 -07:00
a2225f2887 Remove unused include 2025-05-04 15:16:38 -07:00
b43b2f9537 Cleanup standard formatters 2025-05-04 13:04:06 -07:00
1312b4a162 Cleanup standard formatters 2025-05-04 12:37:28 -07:00
4404dc05dd Consolidate implementation details 2025-05-04 10:48:47 -07:00
7bb6fcb325 Bump version 2025-05-04 09:36:07 -07:00
59259a5fde Make a doc directory if it doesn't exist 2025-05-03 10:29:35 -07:00
542ea7c40b Clarify that Formatter parameter is deprecated 2025-05-03 10:28:46 -07:00
22 changed files with 831 additions and 616 deletions

View File

@ -34,7 +34,7 @@ jobs:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # v2.4.0
uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
with:
results_file: results.sarif
results_format: sarif

View File

@ -431,7 +431,7 @@ if (FMT_INSTALL)
# Install the library and headers.
install(TARGETS ${INSTALL_TARGETS}
COMPONENT fmt-core
COMPONENT fmt_core
EXPORT ${targets_export_name}
LIBRARY DESTINATION ${FMT_LIB_DIR}
ARCHIVE DESTINATION ${FMT_LIB_DIR}
@ -447,13 +447,13 @@ if (FMT_INSTALL)
# Install version, config and target files.
install(FILES ${project_config} ${version_config}
DESTINATION ${FMT_CMAKE_DIR}
COMPONENT fmt-core)
COMPONENT fmt_core)
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
NAMESPACE fmt::
COMPONENT fmt-core)
COMPONENT fmt_core)
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
COMPONENT fmt-core)
COMPONENT fmt_core)
endif ()
function(add_doc_target)
@ -490,7 +490,7 @@ function(add_doc_target)
include(GNUInstallDirs)
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
COMPONENT fmt-doc OPTIONAL)
COMPONENT fmt_doc OPTIONAL)
endfunction()
if (FMT_DOC)

View File

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

View File

@ -79,6 +79,8 @@ time formatting and [`fmt/std.h`](#std-api) for other standard library types.
There are two ways to make a user-defined type formattable: providing a
`format_as` function or specializing the `formatter` struct template.
Formatting of non-void pointer types is intentionally disallowed and they
cannot be made formattable via either extension API.
Use `format_as` if you want to make your type formattable as some other
type with the same format specifiers. The `format_as` function should

View File

@ -71,7 +71,7 @@ class dynamic_arg_list {
* It can be implicitly converted into `fmt::basic_format_args` for passing
* into type-erased formatting functions such as `fmt::vformat`.
*/
template <typename Context> class dynamic_format_arg_store {
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
private:
using char_type = typename Context::char_type;

View File

@ -21,7 +21,7 @@
#endif
// The fmt library version in the form major * 10000 + minor * 100 + patch.
#define FMT_VERSION 110200
#define FMT_VERSION 110201
// Detect compiler versions.
#if defined(__clang__) && !defined(__ibmxl__)
@ -467,8 +467,7 @@ template <typename T> constexpr const char* narrow(const T*) { return nullptr; }
constexpr FMT_ALWAYS_INLINE const char* narrow(const char* s) { return s; }
template <typename Char>
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n)
-> int {
FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, size_t n) -> int {
if (!is_constant_evaluated() && sizeof(Char) == 1) return memcmp(s1, s2, n);
for (; n != 0; ++s1, ++s2, --n) {
if (*s1 < *s2) return -1;
@ -540,7 +539,7 @@ template <typename Char> class basic_string_view {
FMT_CONSTEXPR20 basic_string_view(const Char* s) : data_(s) {
#if FMT_HAS_BUILTIN(__builtin_strlen) || FMT_GCC_VERSION || FMT_CLANG_VERSION
if (std::is_same<Char, char>::value && !detail::is_constant_evaluated()) {
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not costexpr.
size_ = __builtin_strlen(detail::narrow(s)); // strlen is not constexpr.
return;
}
#endif
@ -1069,7 +1068,7 @@ template <typename Char> struct named_arg_info {
int id;
};
// named_args is non-const to suppress a bogus -Wmaybe-uninitalized in gcc 13.
// named_args is non-const to suppress a bogus -Wmaybe-uninitialized in gcc 13.
template <typename Char>
FMT_CONSTEXPR void check_for_duplicate(named_arg_info<Char>* named_args,
int named_arg_index,
@ -1679,12 +1678,12 @@ template <typename... T> struct arg_pack {};
template <typename Char, int NUM_ARGS, int NUM_NAMED_ARGS, bool DYNAMIC_NAMES>
class format_string_checker {
private:
type types_[max_of(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of(1, NUM_NAMED_ARGS)];
type types_[max_of<size_t>(1, NUM_ARGS)];
named_arg_info<Char> named_args_[max_of<size_t>(1, NUM_NAMED_ARGS)];
compile_parse_context<Char> context_;
using parse_func = auto (*)(parse_context<Char>&) -> const Char*;
parse_func parse_funcs_[max_of(1, NUM_ARGS)];
parse_func parse_funcs_[max_of<size_t>(1, NUM_ARGS)];
public:
template <typename... T>
@ -2033,6 +2032,17 @@ struct has_back_insert_iterator_container_append<
.append(std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
template <typename OutputIt, typename InputIt, typename = void>
struct has_back_insert_iterator_container_insert_at_end : std::false_type {};
template <typename OutputIt, typename InputIt>
struct has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt,
void_t<decltype(get_container(std::declval<OutputIt>())
.insert(get_container(std::declval<OutputIt>()).end(),
std::declval<InputIt>(),
std::declval<InputIt>()))>> : std::true_type {};
// An optimized version of std::copy with the output value type (T).
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value&&
@ -2047,6 +2057,8 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(is_back_insert_iterator<OutputIt>::value &&
!has_back_insert_iterator_container_append<
OutputIt, InputIt>::value &&
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)>
FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
-> OutputIt {
@ -2056,7 +2068,11 @@ FMT_CONSTEXPR20 auto copy(InputIt begin, InputIt end, OutputIt out)
}
template <typename T, typename InputIt, typename OutputIt,
FMT_ENABLE_IF(!is_back_insert_iterator<OutputIt>::value)>
FMT_ENABLE_IF(!(is_back_insert_iterator<OutputIt>::value &&
(has_back_insert_iterator_container_append<
OutputIt, InputIt>::value ||
has_back_insert_iterator_container_insert_at_end<
OutputIt, InputIt>::value)))>
FMT_CONSTEXPR auto copy(InputIt begin, InputIt end, OutputIt out) -> OutputIt {
while (begin != end) *out++ = static_cast<T>(*begin++);
return out;
@ -2263,6 +2279,7 @@ template <typename Context> class value {
}
// Formats an argument of a custom type, such as a user-defined class.
// DEPRECATED! Formatter template parameter will be removed.
template <typename T, typename Formatter>
static void format_custom(void* arg, parse_context<char_type>& parse_ctx,
Context& ctx) {
@ -2338,8 +2355,9 @@ template <typename Context, int NUM_ARGS, int NUM_NAMED_ARGS,
unsigned long long DESC>
struct named_arg_store {
// args_[0].named_args points to named_args to avoid bloating format_args.
arg_t<Context, NUM_ARGS> args[1 + NUM_ARGS];
named_arg_info<typename Context::char_type> named_args[NUM_NAMED_ARGS];
arg_t<Context, NUM_ARGS> args[1u + NUM_ARGS];
named_arg_info<typename Context::char_type>
named_args[static_cast<size_t>(NUM_NAMED_ARGS)];
template <typename... T>
FMT_CONSTEXPR FMT_ALWAYS_INLINE named_arg_store(T&... values)
@ -2372,7 +2390,7 @@ struct format_arg_store {
// +1 to workaround a bug in gcc 7.5 that causes duplicated-branches warning.
using type =
conditional_t<NUM_NAMED_ARGS == 0,
arg_t<Context, NUM_ARGS>[max_of(1, NUM_ARGS)],
arg_t<Context, NUM_ARGS>[max_of<size_t>(1, NUM_ARGS)],
named_arg_store<Context, NUM_ARGS, NUM_NAMED_ARGS, DESC>>;
type args;
};

View File

@ -326,7 +326,7 @@ inline auto get_classic_locale() -> const std::locale& {
}
template <typename CodeUnit> struct codecvt_result {
static constexpr const size_t max_size = 32;
static constexpr size_t max_size = 32;
CodeUnit buf[max_size];
CodeUnit* end;
};
@ -652,7 +652,7 @@ inline void write_digit2_separated(char* buf, unsigned a, unsigned b,
// Add ASCII '0' to each digit byte and insert separators.
digits |= 0x3030003030003030 | (usep << 16) | (usep << 40);
constexpr const size_t len = 8;
constexpr size_t len = 8;
if (const_check(is_big_endian())) {
char tmp[len];
std::memcpy(tmp, &digits, len);

View File

@ -42,7 +42,7 @@ namespace detail {
#endif
template <typename T, typename... Tail>
auto first(const T& value, const Tail&...) -> const T& {
constexpr auto first(const T& value, const Tail&...) -> const T& {
return value;
}
@ -436,8 +436,8 @@ FMT_BEGIN_EXPORT
template <typename CompiledFormat, typename... Args,
typename Char = typename CompiledFormat::char_type,
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
const Args&... args) {
FMT_INLINE FMT_CONSTEXPR_STRING std::basic_string<Char> format(
const CompiledFormat& cf, const Args&... args) {
auto s = std::basic_string<Char>();
cf.format(std::back_inserter(s), args...);
return s;
@ -452,8 +452,8 @@ constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
template <typename S, typename... Args,
FMT_ENABLE_IF(is_compiled_string<S>::value)>
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
Args&&... args) {
FMT_INLINE FMT_CONSTEXPR_STRING std::basic_string<typename S::char_type> format(
const S&, Args&&... args) {
if constexpr (std::is_same<typename S::char_type, char>::value) {
constexpr auto str = basic_string_view<typename S::char_type>(S());
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {

View File

@ -22,7 +22,7 @@
#include "format.h"
#if FMT_USE_LOCALE
#if FMT_USE_LOCALE && !defined(FMT_MODULE)
# include <locale>
#endif
@ -275,7 +275,7 @@ template <> struct cache_accessor<float> {
static auto get_cached_power(int k) noexcept -> uint64_t {
FMT_ASSERT(k >= float_info<float>::min_k && k <= float_info<float>::max_k,
"k is out of range");
static constexpr const uint64_t pow10_significands[] = {
static constexpr uint64_t pow10_significands[] = {
0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3f,
0xfd87b5f28300ca0e, 0x9e74d1b791e07e49, 0xc612062576589ddb,
0xf79687aed3eec552, 0x9abe14cd44753b53, 0xc16d9a0095928a28,
@ -370,7 +370,7 @@ template <> struct cache_accessor<double> {
FMT_ASSERT(k >= float_info<double>::min_k && k <= float_info<double>::max_k,
"k is out of range");
static constexpr const uint128_fallback pow10_significands[] = {
static constexpr uint128_fallback pow10_significands[] = {
#if FMT_USE_FULL_CACHE_DRAGONBOX
{0xff77b1fcbebcdc4f, 0x25e8e89c13bb0f7b},
{0x9faacf3df73609b1, 0x77b191618c54e9ad},
@ -1037,7 +1037,7 @@ template <> struct cache_accessor<double> {
#if FMT_USE_FULL_CACHE_DRAGONBOX
return pow10_significands[k - float_info<double>::min_k];
#else
static constexpr const uint64_t powers_of_5_64[] = {
static constexpr uint64_t powers_of_5_64[] = {
0x0000000000000001, 0x0000000000000005, 0x0000000000000019,
0x000000000000007d, 0x0000000000000271, 0x0000000000000c35,
0x0000000000003d09, 0x000000000001312d, 0x000000000005f5e1,

View File

@ -44,6 +44,7 @@
# include <cmath> // std::signbit
# include <cstddef> // std::byte
# include <cstdint> // uint32_t
# include <cstdlib> // std::malloc, std::free
# include <cstring> // std::memcpy
# include <limits> // std::numeric_limits
# include <new> // std::bad_alloc
@ -117,6 +118,33 @@
# define FMT_NOINLINE
#endif
// Detect constexpr std::string.
#if !FMT_USE_CONSTEVAL
# define FMT_USE_CONSTEXPR_STRING 0
#elif defined(__cpp_lib_constexpr_string) && \
__cpp_lib_constexpr_string >= 201907L
# if FMT_CLANG_VERSION && FMT_GLIBCXX_RELEASE
// clang + libstdc++ are able to work only starting with gcc13.3
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113294
# if FMT_GLIBCXX_RELEASE < 13
# define FMT_USE_CONSTEXPR_STRING 0
# elif FMT_GLIBCXX_RELEASE == 13 && __GLIBCXX__ < 20240521
# define FMT_USE_CONSTEXPR_STRING 0
# else
# define FMT_USE_CONSTEXPR_STRING 1
# endif
# else
# define FMT_USE_CONSTEXPR_STRING 1
# endif
#else
# define FMT_USE_CONSTEXPR_STRING 0
#endif
#if FMT_USE_CONSTEXPR_STRING
# define FMT_CONSTEXPR_STRING constexpr
#else
# define FMT_CONSTEXPR_STRING
#endif
// GCC 4.9 doesn't support qualified names in specializations.
namespace std {
template <typename T> struct iterator_traits<fmt::basic_appender<T>> {
@ -526,6 +554,8 @@ FMT_CONSTEXPR auto fill_n(OutputIt out, Size count, const T& value)
template <typename T, typename Size>
FMT_CONSTEXPR20 auto fill_n(T* out, Size count, char value) -> T* {
if (is_constant_evaluated()) return fill_n<T*, Size, T>(out, count, value);
static_assert(sizeof(T) == 1,
"sizeof(T) must be 1 to use char for initialization");
std::memset(out, value, to_unsigned(count));
return out + count;
}
@ -555,10 +585,10 @@ FMT_CONSTEXPR FMT_NOINLINE auto copy_noinline(InputIt begin, InputIt end,
*/
FMT_CONSTEXPR inline auto utf8_decode(const char* s, uint32_t* c, int* e)
-> const char* {
constexpr const int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
constexpr const uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
constexpr const int shiftc[] = {0, 18, 12, 6, 0};
constexpr const int shifte[] = {0, 6, 4, 2, 0};
constexpr int masks[] = {0x00, 0x7f, 0x1f, 0x0f, 0x07};
constexpr uint32_t mins[] = {4194304, 0, 128, 2048, 65536};
constexpr int shiftc[] = {0, 18, 12, 6, 0};
constexpr int shifte[] = {0, 6, 4, 2, 0};
int len = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4"
[static_cast<unsigned char>(*s) >> 3];
@ -669,26 +699,6 @@ FMT_CONSTEXPR inline auto compute_width(string_view s) -> size_t {
return num_code_points;
}
template <typename Char>
inline auto code_point_index(basic_string_view<Char> s, size_t n) -> size_t {
return min_of(n, s.size());
}
// Calculates the index of the nth code point in a UTF-8 string.
inline auto code_point_index(string_view s, size_t n) -> size_t {
size_t result = s.size();
const char* begin = s.begin();
for_each_codepoint(s, [begin, &n, &result](uint32_t, string_view sv) {
if (n != 0) {
--n;
return true;
}
result = to_unsigned(sv.begin() - begin);
return false;
});
return result;
}
template <typename T> struct is_integral : std::is_integral<T> {};
template <> struct is_integral<int128_opt> : std::true_type {};
template <> struct is_integral<uint128_t> : std::true_type {};
@ -730,6 +740,9 @@ 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 {};
template <typename T>
using fast_float_t = conditional_t<sizeof(T) == sizeof(double), double, float>;
template <typename T>
using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
@ -738,18 +751,19 @@ using is_double_double = bool_constant<std::numeric_limits<T>::digits == 106>;
#endif
// An allocator that uses malloc/free to allow removing dependency on the C++
// standard libary runtime.
template <typename T> struct allocator {
// standard libary runtime. std::decay is used for back_inserter to be found by
// ADL when applied to memory_buffer.
template <typename T> struct allocator : private std::decay<void> {
using value_type = T;
T* allocate(size_t n) {
FMT_ASSERT(n <= max_value<size_t>() / sizeof(T), "");
T* p = static_cast<T*>(malloc(n * sizeof(T)));
T* p = static_cast<T*>(std::malloc(n * sizeof(T)));
if (!p) FMT_THROW(std::bad_alloc());
return p;
}
void deallocate(T* p, size_t) { free(p); }
void deallocate(T* p, size_t) { std::free(p); }
};
} // namespace detail
@ -1044,7 +1058,7 @@ inline auto do_count_digits(uint64_t n) -> int {
10, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15, 15,
15, 16, 16, 16, 16, 17, 17, 17, 18, 18, 18, 19, 19, 19, 19, 20};
auto t = bsr2log10[FMT_BUILTIN_CLZLL(n | 1) ^ 63];
static constexpr const uint64_t zero_or_powers_of_10[] = {
static constexpr uint64_t zero_or_powers_of_10[] = {
0, 0, FMT_POWERS_OF_10(1U), FMT_POWERS_OF_10(1000000000ULL),
10000000000000000000ULL};
return t - (n < zero_or_powers_of_10[t]);
@ -1225,7 +1239,7 @@ FMT_CONSTEXPR auto do_format_base2e(int base_bits, Char* out, UInt value,
out += size;
do {
const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef";
unsigned digit = static_cast<unsigned>(value & ((1 << base_bits) - 1));
unsigned digit = static_cast<unsigned>(value & ((1u << base_bits) - 1));
*--out = static_cast<Char>(base_bits < 4 ? static_cast<char>('0' + digit)
: digits[digit]);
} while ((value >>= base_bits) != 0);
@ -1487,6 +1501,13 @@ template <typename Float> constexpr auto exponent_bias() -> int {
: std::numeric_limits<Float>::max_exponent - 1;
}
FMT_CONSTEXPR inline auto compute_exp_size(int exp) -> int {
auto prefix_size = 2; // sign + 'e'
auto abs_exp = exp >= 0 ? exp : -exp;
if (exp < 100) return prefix_size + 2;
return prefix_size + (abs_exp >= 1000 ? 4 : 3);
}
// Writes the exponent exp in the form "[+-]d{2,3}" to buffer.
template <typename Char, typename OutputIt>
FMT_CONSTEXPR auto write_exponent(int exp, OutputIt out) -> OutputIt {
@ -1519,7 +1540,7 @@ template <typename F> struct basic_fp {
F f;
int e;
static constexpr const int num_significand_bits =
static constexpr int num_significand_bits =
static_cast<int>(sizeof(F) * num_bits<unsigned char>());
constexpr basic_fp() : f(0), e(0) {}
@ -1964,8 +1985,7 @@ FMT_CONSTEXPR auto make_write_int_arg(T value, sign s)
prefix = 0x01000000 | '-';
abs_value = 0 - abs_value;
} else {
constexpr const unsigned prefixes[4] = {0, 0, 0x1000000u | '+',
0x1000000u | ' '};
constexpr unsigned prefixes[4] = {0, 0, 0x1000000u | '+', 0x1000000u | ' '};
prefix = prefixes[static_cast<int>(s)];
}
return {abs_value, prefix};
@ -2018,7 +2038,7 @@ FMT_CONSTEXPR FMT_INLINE auto write_int(OutputIt out, write_int_arg<T> arg,
const format_specs& specs) -> OutputIt {
static_assert(std::is_same<T, uint32_or_64_or_128_t<T>>::value, "");
constexpr int buffer_size = num_bits<T>();
constexpr size_t buffer_size = num_bits<T>();
char buffer[buffer_size];
if (is_constant_evaluated()) fill_n(buffer, buffer_size, '\0');
const char* begin = nullptr;
@ -2110,13 +2130,35 @@ FMT_CONSTEXPR FMT_INLINE auto write(OutputIt out, T value,
return write_int<Char>(out, make_write_int_arg(value, specs.sign()), specs);
}
inline auto convert_precision_to_size(string_view s, size_t precision)
-> size_t {
size_t display_width = 0;
size_t result = s.size();
for_each_codepoint(s, [&](uint32_t, string_view sv) {
display_width += compute_width(sv);
// Stop when display width exceeds precision.
if (display_width > precision) {
result = to_unsigned(sv.begin() - s.begin());
return false;
}
return true;
});
return result;
}
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
auto convert_precision_to_size(basic_string_view<Char>, size_t precision)
-> size_t {
return precision;
}
template <typename Char, typename OutputIt>
FMT_CONSTEXPR auto write(OutputIt out, basic_string_view<Char> s,
const format_specs& specs) -> OutputIt {
auto data = s.data();
auto size = s.size();
if (specs.precision >= 0 && to_unsigned(specs.precision) < size)
size = code_point_index(s, to_unsigned(specs.precision));
size = convert_precision_to_size(s, to_unsigned(specs.precision));
bool is_debug = specs.type() == presentation_type::debug;
if (is_debug) {
@ -2274,7 +2316,7 @@ inline auto write_significand(Char* out, UInt significand, int significand_size,
int floating_size = significand_size - integral_size;
for (int i = floating_size / 2; i > 0; --i) {
out -= 2;
write2digits(out, static_cast<std::size_t>(significand % 100));
write2digits(out, static_cast<size_t>(significand % 100));
significand /= 100;
}
if (floating_size % 2 != 0) {
@ -2328,110 +2370,18 @@ FMT_CONSTEXPR20 auto write_significand(OutputIt out, T significand,
buffer.end(), out);
}
template <typename Char, typename OutputIt, typename DecimalFP,
typename Grouping = digit_grouping<Char>>
FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
const format_specs& specs, sign s,
int exp_upper, locale_ref loc) -> OutputIt {
auto significand = f.significand;
int significand_size = get_significand_size(f);
const Char zero = static_cast<Char>('0');
size_t size = to_unsigned(significand_size) + (s != sign::none ? 1 : 0);
using iterator = reserve_iterator<OutputIt>;
// Numbers with exponents greater or equal to the returned value will use
// the exponential notation.
template <typename T> FMT_CONSTEVAL auto exp_upper() -> int {
return std::numeric_limits<T>::digits10 != 0
? min_of(16, std::numeric_limits<T>::digits10 + 1)
: 16;
}
Char decimal_point = specs.localized() ? detail::decimal_point<Char>(loc)
: static_cast<Char>('.');
int output_exp = f.exponent + significand_size - 1;
auto use_exp_format = [=]() {
if (specs.type() == presentation_type::exp) return true;
if (specs.type() == presentation_type::fixed) return false;
// Use the fixed notation if the exponent is in [exp_lower, exp_upper),
// e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.
const int exp_lower = -4;
return output_exp < exp_lower ||
output_exp >= (specs.precision > 0 ? specs.precision : exp_upper);
};
if (use_exp_format()) {
int num_zeros = 0;
if (specs.alt()) {
num_zeros = specs.precision - significand_size;
if (num_zeros < 0) num_zeros = 0;
size += to_unsigned(num_zeros);
} else if (significand_size == 1) {
decimal_point = Char();
}
auto abs_output_exp = output_exp >= 0 ? output_exp : -output_exp;
int exp_digits = 2;
if (abs_output_exp >= 100) exp_digits = abs_output_exp >= 1000 ? 4 : 3;
size += to_unsigned((decimal_point ? 1 : 0) + 2 + exp_digits);
char exp_char = specs.upper() ? 'E' : 'e';
auto write = [=](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
// Insert a decimal point after the first digit and add an exponent.
it = write_significand(it, significand, significand_size, 1,
decimal_point);
if (num_zeros > 0) it = detail::fill_n(it, num_zeros, zero);
*it++ = static_cast<Char>(exp_char);
return write_exponent<Char>(output_exp, it);
};
return specs.width > 0
? write_padded<Char, align::right>(out, specs, size, write)
: base_iterator(out, write(reserve(out, size)));
}
int exp = f.exponent + significand_size;
if (f.exponent >= 0) {
// 1234e5 -> 123400000[.0+]
size += to_unsigned(f.exponent);
int num_zeros = specs.precision - exp;
abort_fuzzing_if(num_zeros > 5000);
if (specs.alt()) {
++size;
if (num_zeros <= 0 && specs.type() != presentation_type::fixed)
num_zeros = 0;
if (num_zeros > 0) size += to_unsigned(num_zeros);
}
auto grouping = Grouping(loc, specs.localized());
size += to_unsigned(grouping.count_separators(exp));
return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand<Char>(it, significand, significand_size,
f.exponent, grouping);
if (!specs.alt()) return it;
*it++ = decimal_point;
return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
});
} else if (exp > 0) {
// 1234e-2 -> 12.34[0+]
int num_zeros = specs.alt() ? specs.precision - significand_size : 0;
size += 1 + static_cast<unsigned>(max_of(num_zeros, 0));
auto grouping = Grouping(loc, specs.localized());
size += to_unsigned(grouping.count_separators(exp));
return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand(it, significand, significand_size, exp,
decimal_point, grouping);
return num_zeros > 0 ? detail::fill_n(it, num_zeros, zero) : it;
});
}
// 1234e-6 -> 0.001234
int num_zeros = -exp;
if (significand_size == 0 && specs.precision >= 0 &&
specs.precision < num_zeros) {
num_zeros = specs.precision;
}
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
size += 1 + (pointy ? 1 : 0) + to_unsigned(num_zeros);
return write_padded<Char, align::right>(out, specs, size, [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
*it++ = zero;
if (!pointy) return it;
*it++ = decimal_point;
it = detail::fill_n(it, num_zeros, zero);
return write_significand<Char>(it, significand, significand_size);
});
// Use the fixed notation if the exponent is in [-4, exp_upper),
// e.g. 0.0001 instead of 1e-04. Otherwise use the exponent notation.
constexpr auto use_fixed(int exp, int exp_upper) -> bool {
return exp >= -4 && exp < exp_upper;
}
template <typename Char> class fallback_digit_grouping {
@ -2448,16 +2398,122 @@ template <typename Char> class fallback_digit_grouping {
}
};
template <typename Char, typename Grouping, typename OutputIt,
typename DecimalFP>
FMT_CONSTEXPR20 auto write_fixed(OutputIt out, const DecimalFP& f,
int significand_size, Char decimal_point,
const format_specs& specs, sign s,
locale_ref loc = {}) -> OutputIt {
using iterator = reserve_iterator<OutputIt>;
int exp = f.exponent + significand_size;
long long size = significand_size + (s != sign::none ? 1 : 0);
if (f.exponent >= 0) {
// 1234e5 -> 123400000[.0+]
size += f.exponent;
int num_zeros = specs.precision - exp;
abort_fuzzing_if(num_zeros > 5000);
if (specs.alt()) {
++size;
if (num_zeros <= 0 && specs.type() != presentation_type::fixed)
num_zeros = 0;
if (num_zeros > 0) size += num_zeros;
}
auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp);
return write_padded<Char, align::right>(
out, specs, to_unsigned(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand<Char>(it, f.significand, significand_size,
f.exponent, grouping);
if (!specs.alt()) return it;
*it++ = decimal_point;
return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it;
});
}
if (exp > 0) {
// 1234e-2 -> 12.34[0+]
int num_zeros = specs.alt() ? specs.precision - significand_size : 0;
size += 1 + max_of(num_zeros, 0);
auto grouping = Grouping(loc, specs.localized());
size += grouping.count_separators(exp);
return write_padded<Char, align::right>(
out, specs, to_unsigned(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
it = write_significand(it, f.significand, significand_size, exp,
decimal_point, grouping);
return num_zeros > 0 ? detail::fill_n(it, num_zeros, Char('0')) : it;
});
}
// 1234e-6 -> 0.001234
int num_zeros = -exp;
if (significand_size == 0 && specs.precision >= 0 &&
specs.precision < num_zeros) {
num_zeros = specs.precision;
}
bool pointy = num_zeros != 0 || significand_size != 0 || specs.alt();
size += 1 + (pointy ? 1 : 0) + num_zeros;
return write_padded<Char, align::right>(
out, specs, to_unsigned(size), [&](iterator it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
*it++ = Char('0');
if (!pointy) return it;
*it++ = decimal_point;
it = detail::fill_n(it, num_zeros, Char('0'));
return write_significand<Char>(it, f.significand, significand_size);
});
}
template <typename Char, typename Grouping, typename OutputIt,
typename DecimalFP>
FMT_CONSTEXPR20 auto do_write_float(OutputIt out, const DecimalFP& f,
const format_specs& specs, sign s,
int exp_upper, locale_ref loc) -> OutputIt {
Char point = specs.localized() ? detail::decimal_point<Char>(loc) : Char('.');
int significand_size = get_significand_size(f);
int exp = f.exponent + significand_size - 1;
if (specs.type() == presentation_type::fixed ||
(specs.type() != presentation_type::exp &&
use_fixed(exp, specs.precision > 0 ? specs.precision : exp_upper))) {
return write_fixed<Char, Grouping>(out, f, significand_size, point, specs,
s, loc);
}
// Write value in the exponential format.
int num_zeros = 0;
long long size = significand_size + (s != sign::none ? 1 : 0);
if (specs.alt()) {
num_zeros = max_of(specs.precision - significand_size, 0);
size += num_zeros;
} else if (significand_size == 1) {
point = Char();
}
size += (point ? 1 : 0) + compute_exp_size(exp);
char exp_char = specs.upper() ? 'E' : 'e';
auto write = [=](reserve_iterator<OutputIt> it) {
if (s != sign::none) *it++ = detail::getsign<Char>(s);
// Insert a decimal point after the first digit and add an exponent.
it = write_significand(it, f.significand, significand_size, 1, point);
if (num_zeros > 0) it = detail::fill_n(it, num_zeros, Char('0'));
*it++ = Char(exp_char);
return write_exponent<Char>(exp, it);
};
auto usize = to_unsigned(size);
return specs.width > 0
? write_padded<Char, align::right>(out, specs, usize, write)
: base_iterator(out, write(reserve(out, usize)));
}
template <typename Char, typename OutputIt, typename DecimalFP>
FMT_CONSTEXPR20 auto write_float(OutputIt out, const DecimalFP& f,
const format_specs& specs, sign s,
int exp_upper, locale_ref loc) -> OutputIt {
if (is_constant_evaluated()) {
return do_write_float<Char, OutputIt, DecimalFP,
fallback_digit_grouping<Char>>(out, f, specs, s,
exp_upper, loc);
return do_write_float<Char, fallback_digit_grouping<Char>>(out, f, specs, s,
exp_upper, loc);
} else {
return do_write_float<Char>(out, f, specs, s, exp_upper, loc);
return do_write_float<Char, digit_grouping<Char>>(out, f, specs, s,
exp_upper, loc);
}
}
@ -2728,7 +2784,7 @@ class bigint {
bigits_.resize(to_unsigned(num_bigits + exp_difference));
for (int i = num_bigits - 1, j = i + exp_difference; i >= 0; --i, --j)
bigits_[j] = bigits_[i];
memset(bigits_.data(), 0, to_unsigned(exp_difference) * sizeof(bigit));
fill_n(bigits_.data(), to_unsigned(exp_difference), 0U);
exp_ -= exp_difference;
}
@ -3289,17 +3345,12 @@ FMT_CONSTEXPR20 auto format_float(Float value, int precision,
return exp;
}
// Numbers with exponents greater or equal to the returned value will use
// the exponential notation.
template <typename T> constexpr auto exp_upper() -> int {
return std::numeric_limits<T>::digits10 != 0
? min_of(16, std::numeric_limits<T>::digits10 + 1)
: 16;
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_floating_point<T>::value)>
FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs,
locale_ref loc = {}) -> OutputIt {
if (specs.localized() && write_loc(out, value, specs, loc)) return out;
template <typename Char, typename OutputIt, typename T>
FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
locale_ref loc) -> OutputIt {
// Use signbit because value < 0 is false for NaN.
sign s = detail::signbit(value) ? sign::minus : specs.sign();
@ -3312,15 +3363,14 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
if (specs.width != 0) --specs.width;
}
constexpr int exp_upper = detail::exp_upper<T>();
const int exp_upper = detail::exp_upper<T>();
int precision = specs.precision;
if (precision < 0) {
if (specs.type() != presentation_type::none) {
precision = 6;
} else if (is_fast_float<T>::value && !is_constant_evaluated()) {
// Use Dragonbox for the shortest format.
using floaty = conditional_t<sizeof(T) >= sizeof(double), double, float>;
auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
auto dec = dragonbox::to_decimal(static_cast<fast_float_t<T>>(value));
return write_float<Char>(out, dec, specs, s, exp_upper, loc);
}
}
@ -3352,38 +3402,44 @@ FMT_CONSTEXPR20 auto write_float(OutputIt out, T value, format_specs specs,
return write_float<Char>(out, f, specs, s, exp_upper, loc);
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_floating_point<T>::value)>
FMT_CONSTEXPR20 auto write(OutputIt out, T value, format_specs specs,
locale_ref loc = {}) -> OutputIt {
return specs.localized() && write_loc(out, value, specs, loc)
? out
: write_float<Char>(out, value, specs, loc);
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_fast_float<T>::value)>
FMT_CONSTEXPR20 auto write(OutputIt out, T value) -> OutputIt {
if (is_constant_evaluated()) return write<Char>(out, value, format_specs());
auto s = detail::signbit(value) ? sign::minus : sign::none;
auto mask = exponent_mask<fast_float_t<T>>();
if ((bit_cast<decltype(mask)>(value) & mask) == mask)
return write_nonfinite<Char>(out, std::isnan(value), {}, s);
constexpr auto specs = format_specs();
using floaty = conditional_t<sizeof(T) >= sizeof(double), double, float>;
using floaty_uint = typename dragonbox::float_info<floaty>::carrier_uint;
floaty_uint mask = exponent_mask<floaty>();
if ((bit_cast<floaty_uint>(value) & mask) == mask)
return write_nonfinite<Char>(out, std::isnan(value), specs, s);
auto dec = dragonbox::to_decimal(static_cast<fast_float_t<T>>(value));
int significand_size = count_digits(dec.significand);
int exp = dec.exponent + significand_size - 1;
if (use_fixed(exp, detail::exp_upper<T>())) {
return write_fixed<Char, fallback_digit_grouping<Char>>(
out, dec, significand_size, Char('.'), {}, s);
}
auto dec = dragonbox::to_decimal(static_cast<floaty>(value));
return write_float<Char>(out, dec, specs, s, exp_upper<T>(), {});
// Write value in the exponential format.
auto has_decimal_point = significand_size != 1;
size_t size =
to_unsigned((s != sign::none ? 1 : 0) + significand_size +
(has_decimal_point ? 1 : 0) + compute_exp_size(exp));
auto it = reserve(out, size);
if (s != sign::none) *it++ = Char('-');
// Insert a decimal point after the first digit and add an exponent.
it = write_significand(it, dec.significand, significand_size, 1,
has_decimal_point ? '.' : Char());
*it++ = Char('e');
it = write_exponent<Char>(exp, it);
return base_iterator(out, it);
}
template <typename Char, typename OutputIt, typename T,
FMT_ENABLE_IF(is_floating_point<T>::value &&
!is_fast_float<T>::value)>
inline auto write(OutputIt out, T value) -> OutputIt {
return write<Char>(out, value, format_specs());
return write<Char>(out, value, {});
}
template <typename Char, typename OutputIt>
@ -3825,7 +3881,7 @@ struct formatter<T, Char, void_t<detail::format_as_result<T>>>
* auto s = fmt::format("{}", fmt::ptr(p));
*/
template <typename T> auto ptr(T p) -> const void* {
static_assert(std::is_pointer<T>::value, "");
static_assert(std::is_pointer<T>::value, "fmt::ptr used with non-pointer");
return detail::bit_cast<const void*>(p);
}
@ -3850,13 +3906,14 @@ constexpr auto format_as(Enum e) noexcept -> underlying_t<Enum> {
} // namespace enums
#ifdef __cpp_lib_byte
template <> struct formatter<std::byte> : formatter<unsigned> {
template <typename Char>
struct formatter<std::byte, Char> : formatter<unsigned, Char> {
static auto format_as(std::byte b) -> unsigned char {
return static_cast<unsigned char>(b);
}
template <typename Context>
auto format(std::byte b, Context& ctx) const -> decltype(ctx.out()) {
return formatter<unsigned>::format(format_as(b), ctx);
return formatter<unsigned, Char>::format(format_as(b), ctx);
}
};
#endif
@ -4208,7 +4265,7 @@ FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args)
* std::string answer = fmt::to_string(42);
*/
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
FMT_NODISCARD auto to_string(T value) -> std::string {
FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(T value) -> std::string {
// The buffer should be large enough to store the number including the sign
// or "false" for bool.
char buffer[max_of(detail::digits10<T>() + 2, 5)];
@ -4216,13 +4273,15 @@ FMT_NODISCARD auto to_string(T value) -> std::string {
}
template <typename T, FMT_ENABLE_IF(detail::use_format_as<T>::value)>
FMT_NODISCARD auto to_string(const T& value) -> std::string {
FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value)
-> std::string {
return to_string(format_as(value));
}
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value &&
!detail::use_format_as<T>::value)>
FMT_NODISCARD auto to_string(const T& value) -> std::string {
FMT_NODISCARD FMT_CONSTEXPR_STRING auto to_string(const T& value)
-> std::string {
auto buffer = memory_buffer();
detail::write<char>(appender(buffer), value);
return {buffer.data(), buffer.size()};

View File

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

View File

@ -11,7 +11,6 @@
#ifndef FMT_MODULE
# include <initializer_list>
# include <iterator>
# include <string>
# include <tuple>
# include <type_traits>
# include <utility>
@ -31,7 +30,7 @@ template <typename T> class is_map {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -40,17 +39,16 @@ template <typename T> class is_set {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value && !is_map<T>::value;
};
// C array overload
template <typename T, std::size_t N>
template <typename T, size_t N>
auto range_begin(const T (&arr)[N]) -> const T* {
return arr;
}
template <typename T, std::size_t N>
auto range_end(const T (&arr)[N]) -> const T* {
template <typename T, size_t N> auto range_end(const T (&arr)[N]) -> const T* {
return arr + N;
}
@ -120,7 +118,7 @@ template <typename T> class is_tuple_like_ {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -154,7 +152,7 @@ using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
template <typename T, typename C, bool = is_tuple_like_<T>::value>
class is_tuple_formattable_ {
public:
static constexpr const bool value = false;
static constexpr bool value = false;
};
template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
template <size_t... Is>
@ -170,7 +168,7 @@ template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
C>::value)...>{}));
public:
static constexpr const bool value =
static constexpr bool value =
decltype(check(tuple_index_sequence<T>{}))::value;
};
@ -208,7 +206,7 @@ template <typename Char, typename... T>
using result_t = std::tuple<formatter<remove_cvref_t<T>, Char>...>;
using std::get;
template <typename Tuple, typename Char, std::size_t... Is>
template <typename Tuple, typename Char, size_t... Is>
auto get_formatters(index_sequence<Is...>)
-> result_t<Char, decltype(get<Is>(std::declval<Tuple>()))...>;
} // namespace tuple
@ -219,7 +217,7 @@ template <typename R> struct range_reference_type_impl {
using type = decltype(*detail::range_begin(std::declval<R&>()));
};
template <typename T, std::size_t N> struct range_reference_type_impl<T[N]> {
template <typename T, size_t N> struct range_reference_type_impl<T[N]> {
using type = T&;
};
@ -281,14 +279,15 @@ template <typename FormatContext> struct format_tuple_element {
} // namespace detail
FMT_EXPORT
template <typename T> struct is_tuple_like {
static constexpr const bool value =
static constexpr bool value =
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
};
FMT_EXPORT
template <typename T, typename C> struct is_tuple_formattable {
static constexpr const bool value =
detail::is_tuple_formattable_<T, C>::value;
static constexpr bool value = detail::is_tuple_formattable_<T, C>::value;
};
template <typename Tuple, typename Char>
@ -343,8 +342,9 @@ struct formatter<Tuple, Char,
}
};
FMT_EXPORT
template <typename T, typename Char> struct is_range {
static constexpr const bool value =
static constexpr bool value =
detail::is_range_<T>::value && !detail::has_to_string_view<T>::value;
};
@ -368,6 +368,7 @@ template <typename P1, typename... Pn>
struct conjunction<P1, Pn...>
: conditional_t<bool(P1::value), conjunction<Pn...>, P1> {};
FMT_EXPORT
template <typename T, typename Char, typename Enable = void>
struct range_formatter;
@ -670,7 +671,8 @@ struct formatter<join_view<It, Sentinel, Char>, Char> {
}
};
template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
FMT_EXPORT
template <typename Tuple, typename Char> struct tuple_join_view : detail::view {
const Tuple& tuple;
basic_string_view<Char> sep;
@ -685,15 +687,15 @@ template <typename Char, typename Tuple> struct tuple_join_view : detail::view {
# define FMT_TUPLE_JOIN_SPECIFIERS 0
#endif
template <typename Char, typename Tuple>
struct formatter<tuple_join_view<Char, Tuple>, Char,
template <typename Tuple, typename Char>
struct formatter<tuple_join_view<Tuple, Char>, Char,
enable_if_t<is_tuple_like<Tuple>::value>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return do_parse(ctx, std::tuple_size<Tuple>());
}
template <typename FormatContext>
auto format(const tuple_join_view<Char, Tuple>& value,
auto format(const tuple_join_view<Tuple, Char>& value,
FormatContext& ctx) const -> typename FormatContext::iterator {
return do_format(value, ctx, std::tuple_size<Tuple>());
}
@ -725,14 +727,14 @@ struct formatter<tuple_join_view<Char, Tuple>, Char,
}
template <typename FormatContext>
auto do_format(const tuple_join_view<Char, Tuple>&, FormatContext& ctx,
auto do_format(const tuple_join_view<Tuple, Char>&, FormatContext& ctx,
std::integral_constant<size_t, 0>) const ->
typename FormatContext::iterator {
return ctx.out();
}
template <typename FormatContext, size_t N>
auto do_format(const tuple_join_view<Char, Tuple>& value, FormatContext& ctx,
auto do_format(const tuple_join_view<Tuple, Char>& value, FormatContext& ctx,
std::integral_constant<size_t, N>) const ->
typename FormatContext::iterator {
using std::get;
@ -754,7 +756,7 @@ template <typename T> class is_container_adaptor_like {
template <typename> static void check(...);
public:
static constexpr const bool value =
static constexpr bool value =
!std::is_void<decltype(check<T>(nullptr))>::value;
};
@ -825,7 +827,7 @@ auto join(Range&& r, string_view sep)
*/
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
FMT_CONSTEXPR auto join(const Tuple& tuple, string_view sep)
-> tuple_join_view<char, Tuple> {
-> tuple_join_view<Tuple, char> {
return {tuple, sep};
}

View File

@ -15,15 +15,13 @@
# include <atomic>
# include <bitset>
# include <complex>
# include <cstdlib>
# include <exception>
# include <functional>
# include <functional> // std::reference_wrapper
# include <memory>
# include <thread>
# include <type_traits>
# include <typeinfo>
# include <utility>
# include <vector>
# include <typeinfo> // std::type_info
# include <utility> // std::make_index_sequence
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
# if FMT_CPLUSPLUS >= 201703L
@ -79,11 +77,11 @@
# endif
#endif
#if FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
namespace detail {
#if FMT_CPP_LIB_FILESYSTEM
template <typename Char, typename PathChar>
auto get_path_string(const std::filesystem::path& p,
const std::basic_string<PathChar>& native) {
@ -111,8 +109,169 @@ void write_escaped_path(basic_memory_buffer<Char>& quoted,
}
}
#endif // FMT_CPP_LIB_FILESYSTEM
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
#endif
#if FMT_CPP_LIB_VARIANT
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
template <typename Variant, typename Char> class is_variant_formattable {
template <size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>
check(std::index_sequence<Is...>);
public:
static constexpr bool value = decltype(check(
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
};
#endif // FMT_CPP_LIB_VARIANT
#if FMT_USE_RTTI
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
#endif // FMT_USE_RTTI
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr bool value = std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value &&
has_flip<T>::value;
};
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
#ifdef _LIBCPP_VERSION
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr bool value = true;
};
#endif
template <typename T, typename Enable = void>
struct has_format_as : std::false_type {};
template <typename T>
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
: std::true_type {};
template <typename T, typename Enable = void>
struct has_format_as_member : std::false_type {};
template <typename T>
struct has_format_as_member<
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
: std::true_type {};
} // namespace detail
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
#if FMT_CPP_LIB_FILESYSTEM
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
template <typename Char> struct formatter<std::filesystem::path, Char> {
private:
format_specs specs_;
@ -162,39 +321,20 @@ template <typename Char> struct formatter<std::filesystem::path, Char> {
}
};
class path : public std::filesystem::path {
public:
auto display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{}"), base);
}
auto system_string() const -> std::string { return string(); }
auto generic_display_string() const -> std::string {
const std::filesystem::path& base = *this;
return fmt::format(FMT_STRING("{:g}"), base);
}
auto generic_system_string() const -> std::string { return generic_string(); }
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_FILESYSTEM
FMT_BEGIN_NAMESPACE
template <std::size_t N, typename Char>
template <size_t N, typename Char>
struct formatter<std::bitset<N>, Char>
: nested_formatter<basic_string_view<Char>, Char> {
private:
// Functor because C++11 doesn't support generic lambdas.
// This is a functor because C++11 doesn't support generic lambdas.
struct writer {
const std::bitset<N>& bs;
template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
for (auto pos = N; pos > 0; --pos) {
for (auto pos = N; pos > 0; --pos)
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
}
return out;
}
};
@ -209,10 +349,8 @@ struct formatter<std::bitset<N>, Char>
template <typename Char>
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
FMT_END_NAMESPACE
#ifdef __cpp_lib_optional
FMT_BEGIN_NAMESPACE
template <typename T, typename Char>
struct formatter<std::optional<T>, Char,
std::enable_if_t<is_formattable<T, Char>::value>> {
@ -251,30 +389,9 @@ struct formatter<std::optional<T>, Char,
return detail::write(out, ')');
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_optional
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename Char, typename OutputIt, typename T>
auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt {
if constexpr (has_to_string_view<T>::value)
return write_escaped_string<Char>(out, detail::to_string_view(v));
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
return write<Char>(out, v);
}
} // namespace detail
FMT_END_NAMESPACE
#endif
#ifdef __cpp_lib_expected
FMT_BEGIN_NAMESPACE
template <typename T, typename E, typename Char>
struct formatter<std::expected<T, E>, Char,
std::enable_if_t<(std::is_void<T>::value ||
@ -301,11 +418,9 @@ struct formatter<std::expected<T, E>, Char,
return out;
}
};
FMT_END_NAMESPACE
#endif // __cpp_lib_expected
#ifdef __cpp_lib_source_location
FMT_BEGIN_NAMESPACE
template <> struct formatter<std::source_location> {
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
@ -323,42 +438,12 @@ template <> struct formatter<std::source_location> {
return out;
}
};
FMT_END_NAMESPACE
#endif
#if FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
namespace detail {
template <typename T>
using variant_index_sequence =
std::make_index_sequence<std::variant_size<T>::value>;
template <typename> struct is_variant_like_ : std::false_type {};
template <typename... Types>
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
// formattable element check.
template <typename T, typename C> class is_variant_formattable_ {
template <std::size_t... Is>
static std::conjunction<
is_formattable<std::variant_alternative_t<Is, T>, C>...>
check(std::index_sequence<Is...>);
public:
static constexpr const bool value =
decltype(check(variant_index_sequence<T>{}))::value;
};
} // namespace detail
template <typename T> struct is_variant_like {
static constexpr const bool value = detail::is_variant_like_<T>::value;
};
template <typename T, typename C> struct is_variant_formattable {
static constexpr const bool value =
detail::is_variant_formattable_<T, C>::value;
static constexpr bool value = detail::is_variant_like_<T>::value;
};
template <typename Char> struct formatter<std::monostate, Char> {
@ -374,10 +459,10 @@ template <typename Char> struct formatter<std::monostate, Char> {
};
template <typename Variant, typename Char>
struct formatter<
Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>, is_variant_formattable<Variant, Char>>>> {
struct formatter<Variant, Char,
std::enable_if_t<std::conjunction_v<
is_variant_like<Variant>,
detail::is_variant_formattable<Variant, Char>>>> {
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
return ctx.begin();
}
@ -402,10 +487,9 @@ struct formatter<
return out;
}
};
FMT_END_NAMESPACE
#endif // FMT_CPP_LIB_VARIANT
FMT_BEGIN_NAMESPACE
template <> struct formatter<std::error_code> {
private:
format_specs specs_;
@ -459,76 +543,6 @@ template <> struct formatter<std::error_code> {
};
#if FMT_USE_RTTI
namespace detail {
template <typename Char, typename OutputIt>
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
int status = 0;
std::size_t size = 0;
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &std::free);
string_view demangled_name_view;
if (demangled_name_ptr) {
demangled_name_view = demangled_name_ptr.get();
// Normalization of stdlib inline namespace names.
// libc++ inline namespaces.
// std::__1::* -> std::*
// std::__1::__fs::* -> std::*
// libstdc++ inline namespaces.
// std::__cxx11::* -> std::*
// std::filesystem::__cxx11::* -> std::filesystem::*
if (demangled_name_view.starts_with("std::")) {
char* begin = demangled_name_ptr.get();
char* to = begin + 5; // std::
for (char *from = to, *end = begin + demangled_name_view.size();
from < end;) {
// This is safe, because demangled_name is NUL-terminated.
if (from[0] == '_' && from[1] == '_') {
char* next = from + 1;
while (next < end && *next != ':') next++;
if (next[0] == ':' && next[1] == ':') {
from = next + 2;
continue;
}
}
*to++ = *from++;
}
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
}
} else {
demangled_name_view = string_view(ti.name());
}
return detail::write_bytes<Char>(out, demangled_name_view);
# elif FMT_MSC_VERSION
const string_view demangled_name(ti.name());
for (std::size_t i = 0; i < demangled_name.size(); ++i) {
auto sub = demangled_name;
sub.remove_prefix(i);
if (sub.starts_with("enum ")) {
i += 4;
continue;
}
if (sub.starts_with("class ") || sub.starts_with("union ")) {
i += 5;
continue;
}
if (sub.starts_with("struct ")) {
i += 6;
continue;
}
if (*sub.begin() != ' ') *out++ = *sub.begin();
}
return out;
# else
return detail::write_bytes<Char>(out, string_view(ti.name()));
# endif
}
} // namespace detail
template <typename Char>
struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
> {
@ -543,7 +557,7 @@ struct formatter<std::type_info, Char // DEPRECATED! Mixing code unit types.
return detail::write_demangled_name<Char>(ctx.out(), ti);
}
};
#endif
#endif // FMT_USE_RTTI
template <typename T, typename Char>
struct formatter<
@ -579,34 +593,6 @@ struct formatter<
}
};
namespace detail {
template <typename T, typename Enable = void>
struct has_flip : std::false_type {};
template <typename T>
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
: std::true_type {};
template <typename T> struct is_bit_reference_like {
static constexpr const bool value =
std::is_convertible<T, bool>::value &&
std::is_nothrow_assignable<T, bool>::value && has_flip<T>::value;
};
#ifdef _LIBCPP_VERSION
// Workaround for libc++ incompatibility with C++ standard.
// According to the Standard, `bitset::operator[] const` returns bool.
template <typename C>
struct is_bit_reference_like<std::__bit_const_reference<C>> {
static constexpr const bool value = true;
};
#endif
} // namespace detail
// We can't use std::vector<bool, Allocator>::reference and
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
// in partial specialization.
@ -621,14 +607,6 @@ struct formatter<BitRef, Char,
}
};
template <typename T, typename Deleter>
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
return p.get();
}
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
return p.get();
}
template <typename T, typename Char>
struct formatter<std::atomic<T>, Char,
enable_if_t<is_formattable<T, Char>::value>>
@ -715,7 +693,11 @@ template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
template <typename T, typename Char>
struct formatter<std::reference_wrapper<T>, Char,
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value>>
// Guard against format_as because reference_wrapper is
// implicitly convertible to T&.
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
!detail::has_format_as<T>::value &&
!detail::has_format_as_member<T>::value>>
: formatter<remove_cvref_t<T>, Char> {
template <typename FormatContext>
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
@ -725,4 +707,5 @@ struct formatter<std::reference_wrapper<T>, Char,
};
FMT_END_NAMESPACE
#endif // FMT_STD_H_

View File

@ -151,7 +151,7 @@ auto join(std::initializer_list<T> list, wstring_view sep)
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
-> tuple_join_view<wchar_t, Tuple> {
-> tuple_join_view<Tuple, wchar_t> {
return {tuple, sep};
}

View File

@ -66,14 +66,14 @@ using rwresult = int;
// On Windows the count argument to read and write is unsigned, so convert
// it from size_t preventing integer overflow.
inline unsigned convert_rwcount(std::size_t count) {
inline unsigned convert_rwcount(size_t count) {
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
}
#elif FMT_USE_FCNTL
// Return type of read and write functions.
using rwresult = ssize_t;
inline std::size_t convert_rwcount(std::size_t count) { return count; }
inline size_t convert_rwcount(size_t count) { return count; }
#endif
} // namespace
@ -266,7 +266,7 @@ long long file::size() const {
# endif
}
std::size_t file::read(void* buffer, std::size_t count) {
size_t file::read(void* buffer, size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
if (result < 0)
@ -274,7 +274,7 @@ std::size_t file::read(void* buffer, std::size_t count) {
return detail::to_unsigned(result);
}
std::size_t file::write(const void* buffer, std::size_t count) {
size_t file::write(const void* buffer, size_t count) {
rwresult result = 0;
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
if (result < 0)

View File

@ -63,7 +63,9 @@ if len(args) > 0:
'--branch', 'master'], cwd=site_dir, env=env)
if ret != 0 or version == 'dev':
sys.exit(ret)
redirect_page_path = os.path.join(site_dir, version, 'api.html')
current_doc_path = os.path.join(site_dir, version)
os.makedirs(current_doc_path, exist_ok=True)
redirect_page_path = os.path.join(current_doc_path, 'api.html')
with open(redirect_page_path, "w") as file:
file.write(redirect_page)
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)

View File

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

View File

@ -421,3 +421,24 @@ TEST(compile_time_formatting_test, multibyte_fill) {
EXPECT_EQ("жж42", test_format<8>(FMT_COMPILE("{:ж>4}"), 42));
}
#endif
#if FMT_USE_CONSTEXPR_STRING
TEST(compile_test, constexpr_format) {
{
constexpr auto result = []() {
return fmt::format(FMT_COMPILE("{}"), 42) == "42";
}();
EXPECT_TRUE(result);
}
{
// Test with a larger string to avoid small string optimization.
constexpr auto result = []() {
return fmt::format(FMT_COMPILE("{:100}"), ' ') == std::string(100, ' ');
}();
EXPECT_TRUE(result);
}
}
#endif // FMT_USE_CONSTEXPR_STRING

View File

@ -481,6 +481,12 @@ TEST(memory_buffer_test, max_size_allocator_overflow) {
EXPECT_THROW(buffer.resize(161), std::exception);
}
TEST(memory_buffer_test, back_insert_iterator) {
fmt::memory_buffer buf;
using iterator = decltype(std::back_inserter(buf));
EXPECT_TRUE(fmt::detail::is_back_insert_iterator<iterator>::value);
}
TEST(format_test, digits2_alignment) {
auto p =
fmt::detail::bit_cast<fmt::detail::uintptr_t>(fmt::detail::digits2(0));
@ -552,6 +558,10 @@ TEST(format_test, arg_errors) {
format_error, "argument not found");
}
TEST(format_test, display_width_precision) {
EXPECT_EQ(fmt::format("{:.5}", "🐱🐱🐱"), "🐱🐱");
}
template <int N> struct test_format {
template <typename... T>
static auto format(fmt::string_view fmt, const T&... args) -> std::string {
@ -1085,9 +1095,6 @@ TEST(format_test, precision) {
EXPECT_THROW_MSG(
(void)fmt::format(runtime("{0:.2f}"), reinterpret_cast<void*>(0xcafe)),
format_error, "invalid format specifier");
EXPECT_THROW_MSG((void)fmt::format(runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
EXPECT_THROW_MSG(
(void)fmt::format("{:.2147483646f}", -2.2121295195081227E+304),
format_error, "number is too big");
@ -1099,6 +1106,32 @@ TEST(format_test, precision) {
EXPECT_EQ(fmt::format("{0:.6}", "123456\xad"), "123456");
}
TEST(format_test, large_precision) {
// Iterator used to abort the actual output.
struct throwing_iterator {
auto operator=(char) -> throwing_iterator& {
throw std::runtime_error("aborted");
return *this;
}
auto operator*() -> throwing_iterator& { return *this; }
auto operator++() -> throwing_iterator& { return *this; }
auto operator++(int) -> throwing_iterator { return *this; }
};
auto it = throwing_iterator();
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}}"), 1.0,
fmt::detail::max_value<int>()),
std::runtime_error, "aborted");
EXPECT_THROW_MSG(fmt::format_to(it, fmt::runtime("{:#.{}e}"), 1.0,
fmt::detail::max_value<int>() - 1),
std::runtime_error, "aborted");
EXPECT_THROW_MSG((void)fmt::format(fmt::runtime("{:.{}e}"), 42.0,
fmt::detail::max_value<int>()),
format_error, "number is too big");
}
TEST(format_test, utf8_precision) {
auto result = fmt::format("{:.4}", "caf\u00e9s"); // cafés
EXPECT_EQ(fmt::detail::compute_width(result), 4);
@ -1940,8 +1973,8 @@ TEST(format_test, unpacked_args) {
constexpr char with_null[3] = {'{', '}', '\0'};
constexpr char no_null[2] = {'{', '}'};
static constexpr const char static_with_null[3] = {'{', '}', '\0'};
static constexpr const char static_no_null[2] = {'{', '}'};
static constexpr char static_with_null[3] = {'{', '}', '\0'};
static constexpr char static_no_null[2] = {'{', '}'};
TEST(format_test, compile_time_string) {
EXPECT_EQ(fmt::format(FMT_STRING("foo")), "foo");

View File

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

View File

@ -413,5 +413,31 @@ TEST(std_test, format_shared_ptr) {
TEST(std_test, format_reference_wrapper) {
int num = 35;
EXPECT_EQ("35", fmt::to_string(std::cref(num)));
EXPECT_EQ(fmt::to_string(std::cref(num)), "35");
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
}
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
struct type_with_format_as {};
int format_as(type_with_format_as) { return 20; }
TEST(std_test, format_reference_wrapper_with_format_as) {
type_with_format_as t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
}
struct type_with_format_as_string {};
std::string format_as(type_with_format_as_string) { return "foo"; }
TEST(std_test, format_reference_wrapper_with_format_as_string) {
type_with_format_as_string t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
}

View File

@ -171,6 +171,13 @@ TEST(xchar_test, join) {
EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)");
}
#ifdef __cpp_lib_byte
TEST(xchar_test, join_bytes) {
auto v = std::vector<std::byte>{std::byte(1), std::byte(2), std::byte(3)};
EXPECT_EQ(fmt::format(L"{}", fmt::join(v, L", ")), L"1, 2, 3");
}
#endif
enum streamable_enum {};
std::wostream& operator<<(std::wostream& os, streamable_enum) {