diff --git a/.gitignore b/.gitignore index 694f8f8f..208b808d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/ +.vs/ *.iml .idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt index b0928961..3a487ce7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ option(FMT_INSTALL "Generate the install target." ${MASTER_PROJECT}) option(FMT_TEST "Generate the test target." ${MASTER_PROJECT}) option(FMT_FUZZ "Generate the fuzz target." OFF) option(FMT_CUDA_TEST "Generate the cuda-test target." OFF) - +option(FMT_OS "Include core requiring OS (Windows/Posix) " ON) project(FMT CXX) # Get version from core.h @@ -81,6 +81,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} include(cxx14) include(CheckCXXCompilerFlag) +include(JoinPaths) list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index) if (${index} GREATER -1) @@ -173,7 +174,11 @@ endfunction() # Define the fmt library, its includes and the needed defines. add_headers(FMT_HEADERS chrono.h color.h compile.h core.h format.h format-inl.h locale.h os.h ostream.h posix.h printf.h ranges.h) -set(FMT_SOURCES src/format.cc src/os.cc) +if (FMT_OS) + set(FMT_SOURCES src/format.cc src/os.cc) +else() + set(FMT_SOURCES src/format.cc) +endif () add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst) add_library(fmt::fmt ALIAS fmt) @@ -201,8 +206,9 @@ set_target_properties(fmt PROPERTIES VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR} DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}") -# Set FMT_LIB_NAME for pkg-config fmt.pc. -get_target_property(FMT_LIB_NAME fmt OUTPUT_NAME) +# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target +# property because it's not set by default. +set(FMT_LIB_NAME fmt) if (CMAKE_BUILD_TYPE STREQUAL "Debug") set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX}) endif () @@ -234,8 +240,8 @@ if (FMT_INSTALL) include(GNUInstallDirs) include(CMakePackageConfigHelpers) set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING - "Installation directory for cmake files, relative to " - "${CMAKE_INSTALL_PREFIX}.") + "Installation directory for cmake files, a relative path " + "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake) set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake) set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc) @@ -247,22 +253,26 @@ if (FMT_INSTALL) endif () set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING - "Installation directory for libraries, relative to " - "${CMAKE_INSTALL_PREFIX}.") + "Installation directory for libraries, a relative path " + "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") - set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRINGS - "Installation directory for include files, relative to " - "${CMAKE_INSTALL_PREFIX}.") + set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING + "Installation directory for include files, a relative path " + "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH - "Installation directory for pkgconfig (.pc) files, relative to " - "${CMAKE_INSTALL_PREFIX}.") + "Installation directory for pkgconfig (.pc) files, a relative path " + "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") # Generate the version, config and target files into the build directory. write_basic_package_version_file( ${version_config} VERSION ${FMT_VERSION} COMPATIBILITY AnyNewerVersion) + + join_paths(libdir_for_pc_file "\${exec_prefix}" "${CMAKE_INSTALL_LIBDIR}") + join_paths(includedir_for_pc_file "\${prefix}" "${CMAKE_INSTALL_INCLUDEDIR}") + configure_file( "${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in" "${pkgconfig}" @@ -307,6 +317,7 @@ endif () # Control fuzzing independent of the unit tests. if (FMT_FUZZ) add_subdirectory(test/fuzzing) + target_compile_definitions(fmt PUBLIC FMT_FUZZ) endif () set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore) diff --git a/ChangeLog.rst b/ChangeLog.rst index 75a009e4..8e581c47 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,216 @@ +6.2.0 - 2020-04-05 +------------------ + +* Improved error reporting when trying to format an object of a non-formattable + type: + + .. code:: c++ + + fmt::format("{}", S()); + + now gives:: + + include/fmt/core.h:1015:5: error: static_assert failed due to requirement + 'formattable' "Cannot format argument. To make type T formattable provide a + formatter specialization: + https://fmt.dev/latest/api.html#formatting-user-defined-types" + static_assert( + ^ + ... + note: in instantiation of function template specialization + 'fmt::v6::format' requested here + fmt::format("{}", S()); + ^ + + if ``S`` is not formattable. + +* Reduced library size by ~10%. + +* Always print decimal point if ``#`` is specified + (`#1476 `_, + `#1498 `_): + + .. code:: c++ + + fmt::print("{:#.0f}", 42.0); + + now prints ``42.`` + +* Implemented the ``'L'`` specifier for locale-specific numeric formatting to + improve compatibility with ``std::format``. The ``'n'`` specifier is now + deprecated and will be removed in the next major release. + +* Moved OS-specific APIs such as ``windows_error`` from ``fmt/format.h`` to + ``fmt/os.h``. You can define ``FMT_DEPRECATED_INCLUDE_OS`` to automatically + include ``fmt/os.h`` from ``fmt/format.h`` for compatibility but this will be + disabled in the next major release. + +* Added precision overflow detection in floating-point formatting. + +* Implemented detection of invalid use of ``fmt::arg``. + +* Used ``type_identity`` to block unnecessary template argument deduction. + Thanks Tim Song. + +* Improved UTF-8 handling + (`#1109 `_): + + .. code:: c++ + + fmt::print("┌{0:─^{2}}┐\n" + "│{1: ^{2}}│\n" + "└{0:─^{2}}┘\n", "", "Привет, мир!", 20); + + now prints:: + + ┌────────────────────┐ + │ Привет, мир! │ + └────────────────────┘ + + on systems that support Unicode. + +* Added experimental dynamic argument storage + (`#1170 `_, + `#1584 `_): + + .. code:: c++ + + fmt::dynamic_format_arg_store store; + store.push_back("answer"); + store.push_back(42); + fmt::vprint("The {} is {}.\n", store); + + prints:: + + The answer is 42. + + Thanks `@vsolontsov-ll (Vladimir Solontsov) + `_. + +* Made ``fmt::join`` accept ``initializer_list`` + (`#1591 `_). + Thanks `@Rapotkinnik (Nikolay Rapotkin) `_. + +* Fixed handling of empty tuples + (`#1588 `_). + +* Fixed handling of output iterators in ``format_to_n`` + (`#1506 `_). + +* Fixed formatting of ``std::chrono::duration`` types to wide output + (`#1533 `_). + Thanks `@zeffy (pilao) `_. + +* Added const ``begin`` and ``end`` overload to buffers + (`#1553 `_). + Thanks `@dominicpoeschko `_. + +* Added the ability to disable floating-point formatting via ``FMT_USE_FLOAT``, + ``FMT_USE_DOUBLE`` and ``FMT_USE_LONG_DOUBLE`` macros for extremely + memory-constrained embedded system + (`#1590 `_). + Thanks `@albaguirre (Alberto Aguirre) `_. + +* Made ``FMT_STRING`` work with ``constexpr`` ``string_view`` + (`#1589 `_). + Thanks `@scramsby (Scott Ramsby) `_. + +* Implemented a minor optimization in the format string parser + (`#1560 `_). + Thanks `@IkarusDeveloper `_. + +* Improved attribute detection + (`#1469 `_, + `#1475 `_, + `#1576 `_). + Thanks `@federico-busato (Federico) `_, + `@chronoxor (Ivan Shynkarenka) `_, + `@refnum `_. + +* Improved documentation + (`#1481 `_, + `#1523 `_). + Thanks `@JackBoosY (Jack·Boos·Yu) `_, + `@imba-tjd (谭九鼎) `_. + +* Fixed symbol visibility on Linux when compiling with ``-fvisibility=hidden`` + (`#1535 `_). + Thanks `@milianw (Milian Wolff) `_. + +* Implemented various build configuration fixes and improvements + (`#1264 `_, + `#1460 `_, + `#1534 `_, + `#1536 `_, + `#1545 `_, + `#1546 `_, + `#1566 `_, + `#1582 `_, + `#1597 `_, + `#1598 `_). + Thanks `@ambitslix (Attila M. Szilagyi) `_, + `@jwillikers (Jordan Williams) `_, + `@stac47 (Laurent Stacul) `_. + +* Fixed various warnings and compilation issues + (`#1433 `_, + `#1461 `_, + `#1470 `_, + `#1480 `_, + `#1485 `_, + `#1492 `_, + `#1493 `_, + `#1504 `_, + `#1505 `_, + `#1512 `_, + `#1515 `_, + `#1516 `_, + `#1518 `_, + `#1519 `_, + `#1520 `_, + `#1521 `_, + `#1522 `_, + `#1524 `_, + `#1530 `_, + `#1531 `_, + `#1532 `_, + `#1539 `_, + `#1547 `_, + `#1548 `_, + `#1554 `_, + `#1567 `_, + `#1568 `_, + `#1569 `_, + `#1571 `_, + `#1573 `_, + `#1575 `_, + `#1581 `_, + `#1583 `_, + `#1586 `_, + `#1587 `_, + `#1594 `_, + `#1596 `_, + `#1604 `_, + `#1606 `_, + `#1607 `_, + `#1609 `_). + Thanks `@marti4d (Chris Martin) `_, + `@iPherian `_, + `@parkertomatoes `_, + `@gsjaardema (Greg Sjaardema) `_, + `@chronoxor (Ivan Shynkarenka) `_, + `@DanielaE (Daniela Engert) `_, + `@torsten48 `_, + `@tohammer (Tobias Hammer) `_, + `@lefticus (Jason Turner) `_, + `@ryusakki (Haise) `_, + `@adnsv (Alex Denisov) `_, + `@fghzxm `_, + `@refnum `_, + `@pramodk (Pramod Kumbhar) `_, + `@Spirrwell `_, + `@scramsby (Scott Ramsby) `_. + 6.1.2 - 2019-12-11 ------------------ diff --git a/README.rst b/README.rst index af9f9252..35095cad 100644 --- a/README.rst +++ b/README.rst @@ -264,8 +264,8 @@ or the bloat test:: Projects using this library --------------------------- -* `0 A.D. `_: A free, open-source, cross-platform real-time - strategy game +* `0 A.D. `_: A free, open-source, cross-platform + real-time strategy game * `AMPL/MP `_: An open-source library for mathematical programming @@ -282,6 +282,16 @@ Projects using this library * `CUAUV `_: Cornell University's autonomous underwater vehicle +* `Drake `_: A planning, control, and analysis toolbox + for nonlinear dynamical systems (MIT) + +* `Envoy `_: C++ L7 proxy and communication bus + (Lyft) + +* `FiveM `_: a modification framework for GTA V + +* `Folly `_: Facebook open-source library + * `HarpyWar/pvpgn `_: Player vs Player Gaming Network with tweaks @@ -293,25 +303,20 @@ Projects using this library * `Lifeline `_: A 2D game -* `Drake `_: A planning, control, and analysis toolbox - for nonlinear dynamical systems (MIT) - -* `Envoy `_: C++ L7 proxy and communication bus - (Lyft) - -* `FiveM `_: a modification framework for GTA V - * `MongoDB `_: Distributed document database * `MongoDB Smasher `_: A small tool to generate randomized datasets -* `OpenSpace `_: An open-source astrovisualization - framework +* `OpenSpace `_: An open-source + astrovisualization framework * `PenUltima Online (POL) `_: An MMO server, compatible with most Ultima Online clients +* `PyTorch `_: An open-source machine + learning library + * `quasardb `_: A distributed, high-performance, associative database @@ -320,13 +325,14 @@ Projects using this library * `redis-cerberus `_: A Redis cluster proxy +* `redpanda `_: A 10x faster Kafka® replacement + for mission critical systems written in C++ + * `rpclib `_: A modern C++ msgpack-RPC server and client library -* `Saddy `_: - Small crossplatform 2D graphic engine - -* `Salesforce Analytics Cloud `_: +* `Salesforce Analytics Cloud + `_: Business intelligence software * `Scylla `_: A Cassandra-compatible NoSQL data store @@ -344,6 +350,9 @@ Projects using this library * `TrinityCore `_: Open-source MMORPG framework +* `Windows Terminal `_: The new Windows + Terminal + `More... `_ If you are aware of other projects using this library, please let me know diff --git a/doc/api.rst b/doc/api.rst index 4e370252..8da96d04 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -9,7 +9,8 @@ The {fmt} library API consists of the following parts: * :ref:`fmt/core.h `: the core API providing argument handling facilities and a lightweight subset of formatting functions * :ref:`fmt/format.h `: the full format API providing compile-time - format string checks, output iterator and user-defined type support + format string checks, wide string, output iterator and user-defined type + support * :ref:`fmt/ranges.h `: additional formatting support for ranges and tuples * :ref:`fmt/chrono.h `: date and time formatting @@ -43,7 +44,7 @@ participate in an overload resolution if the latter is not a string. .. _format: .. doxygenfunction:: format(const S&, Args&&...) -.. doxygenfunction:: vformat(const S&, basic_format_args>) +.. doxygenfunction:: vformat(const S&, basic_format_args>>) .. _print: @@ -104,7 +105,7 @@ Format API ========== ``fmt/format.h`` defines the full format API providing compile-time format -string checks, output iterator and user-defined type support. +string checks, wide string, output iterator and user-defined type support. Compile-time Format String Checks --------------------------------- @@ -178,8 +179,7 @@ example:: enum class color {red, green, blue}; - template <> - struct fmt::formatter: formatter { + template <> struct fmt::formatter: formatter { // parse is inherited from formatter. template auto format(color c, FormatContext& ctx) { @@ -193,6 +193,15 @@ example:: } }; +Since ``parse`` is inherited from ``formatter`` it will recognize +all string format specifications, for example + +.. code-block:: c++ + + fmt::format("{:>10}", color::blue) + +will return ``" blue"``. + You can also write a formatter for a hierarchy of classes:: #include @@ -399,7 +408,7 @@ formatting:: std::time_t t = std::time(nullptr); // Prints "The date is 2016-04-29." (with the current date) - fmt::print("The date is {:%Y-%m-%d}.", *std::localtime(&t)); + fmt::print("The date is {:%Y-%m-%d}.", fmt::localtime(t)); The format string syntax is described in the documentation of `strftime `_. diff --git a/doc/build.py b/doc/build.py index 7133c7b5..a7bfcf73 100755 --- a/doc/build.py +++ b/doc/build.py @@ -6,7 +6,7 @@ import errno, os, shutil, sys, tempfile from subprocess import check_call, check_output, CalledProcessError, Popen, PIPE from distutils.version import LooseVersion -versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2'] +versions = ['1.0.0', '1.1.0', '2.0.0', '3.0.2', '4.0.0', '4.1.0', '5.0.0', '5.1.0', '5.2.0', '5.2.1', '5.3.0', '6.0.0', '6.1.0', '6.1.1', '6.1.2', '6.2.0'] def pip_install(package, commit=None, **kwargs): "Install package using pip." diff --git a/doc/syntax.rst b/doc/syntax.rst index af65616a..bf2f1b6b 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -81,8 +81,8 @@ The general form of a *standard format specifier* is: sign: "+" | "-" | " " width: `integer` | "{" `arg_id` "}" precision: `integer` | "{" `arg_id` "}" - type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "p" | "s" - int_type: "b" | "B" | "d" | "n" | "o" | "x" | "X" + type: `int_type` | "a" | "A" | "c" | "e" | "E" | "f" | "F" | "g" | "G" | "L" | "p" | "s" + int_type: "b" | "B" | "d" | "o" | "x" | "X" The *fill* character can be any Unicode code point other than ``'{'`` or ``'}'``. The presence of a fill character is signaled by the character following @@ -143,7 +143,7 @@ conversions, trailing zeros are not removed from the result. .. ifconfig:: False The ``','`` option signals the use of a comma for a thousands separator. - For a locale aware separator, use the ``'n'`` integer presentation type + For a locale aware separator, use the ``'L'`` integer presentation type instead. *width* is a decimal integer defining the minimum field width. If not @@ -214,9 +214,9 @@ The available integer presentation types are: | | ``'#'`` option with this type adds the prefix ``"0X"`` | | | to the output value. | +---------+----------------------------------------------------------+ -| ``'n'`` | Number. This is the same as ``'d'``, except that it uses | -| | the current locale setting to insert the appropriate | -| | number separator characters. | +| ``'L'`` | Locale-specific format. This is the same as ``'d'``, | +| | except that it uses the current locale setting to insert | +| | the appropriate number separator characters. | +---------+----------------------------------------------------------+ | none | The same as ``'d'``. | +---------+----------------------------------------------------------+ @@ -261,9 +261,9 @@ The available presentation types for floating-point values are: | | ``'E'`` if the number gets too large. The | | | representations of infinity and NaN are uppercased, too. | +---------+----------------------------------------------------------+ -| ``'n'`` | Number. This is the same as ``'g'``, except that it uses | -| | the current locale setting to insert the appropriate | -| | number separator characters. | +| ``'L'`` | Locale-specific format. This is the same as ``'g'``, | +| | except that it uses the current locale setting to insert | +| | the appropriate number separator characters. | +---------+----------------------------------------------------------+ | none | Similar to ``'g'``, except that fixed-point notation, | | | when used, has at least one digit past the decimal | @@ -403,7 +403,7 @@ Using the comma as a thousands separator:: #include - auto s = fmt::format(std::locale("en_US.UTF-8"), "{:n}", 1234567890); + auto s = fmt::format(std::locale("en_US.UTF-8"), "{:L}", 1234567890); // s == "1,234,567,890" .. ifconfig:: False diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 421d464a..29f7a250 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -768,17 +768,23 @@ OutputIt format_duration_value(OutputIt out, Rep val, int precision) { return format_to(out, std::is_floating_point::value ? fp_f : format, val); } +template +OutputIt copy_unit(string_view unit, OutputIt out, Char) { + return std::copy(unit.begin(), unit.end(), out); +} + +template +OutputIt copy_unit(string_view unit, OutputIt out, wchar_t) { + // This works when wchar_t is UTF-32 because units only contain characters + // that have the same representation in UTF-16 and UTF-32. + utf8_to_utf16 u(unit); + return std::copy(u.c_str(), u.c_str() + u.size(), out); +} template OutputIt format_duration_unit(OutputIt out) { - if (const char* unit = get_units()) { - string_view s(unit); - if (const_check(std::is_same())) { - utf8_to_utf16 u(s); - return std::copy(u.c_str(), u.c_str() + u.size(), out); - } - return std::copy(s.begin(), s.end(), out); - } + if (const char* unit = get_units()) + return copy_unit(string_view(unit), out, Char()); const Char num_f[] = {'[', '{', '}', ']', 's', 0}; if (Period::den == 1) return format_to(out, num_f, Period::num); const Char num_def_f[] = {'[', '{', '}', '/', '{', '}', ']', 's', 0}; diff --git a/include/fmt/color.h b/include/fmt/color.h index 3756ba3f..96d9ab6b 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -412,7 +412,7 @@ template struct ansi_color_escape { FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; } FMT_CONSTEXPR const Char* end() const FMT_NOEXCEPT { - return buffer + std::strlen(buffer); + return buffer + std::char_traits::length(buffer); } private: diff --git a/include/fmt/compile.h b/include/fmt/compile.h index e4b12f34..75b33313 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -576,7 +576,9 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf, } template ::value)> + FMT_ENABLE_IF( + internal::is_output_iterator::value&& std::is_base_of< + internal::basic_compiled_format, CompiledFormat>::value)> format_to_n_result format_to_n(OutputIt out, size_t n, const CompiledFormat& cf, const Args&... args) { diff --git a/include/fmt/core.h b/include/fmt/core.h index dd71e5ad..2e5a51fe 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -18,8 +18,45 @@ #include // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 60103 +#define FMT_VERSION 60201 +#ifdef __clang__ +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif + +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#else +# define FMT_ICC_VERSION 0 +#endif + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#else +# define FMT_HAS_GXX_CXX11 0 +#endif + +#ifdef __NVCC__ +# define FMT_NVCC __NVCC__ +#else +# define FMT_NVCC 0 +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n)) +#else +# define FMT_MSC_VER 0 +# define FMT_SUPPRESS_MSC_WARNING(n) +#endif #ifdef __has_feature # define FMT_HAS_FEATURE(x) __has_feature(x) #else @@ -27,7 +64,7 @@ #endif #if defined(__has_include) && !defined(__INTELLISENSE__) && \ - !(defined(__INTEL_COMPILER) && __INTEL_COMPILER < 1600) + !(FMT_ICC_VERSION && FMT_ICC_VERSION < 1600) # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -45,43 +82,13 @@ #define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) -#ifdef __clang__ -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#else -# define FMT_GCC_VERSION 0 -#endif - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION -#else -# define FMT_HAS_GXX_CXX11 0 -#endif - -#ifdef __NVCC__ -# define FMT_NVCC __NVCC__ -#else -# define FMT_NVCC 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER -#else -# define FMT_MSC_VER 0 -#endif - // 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_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ - !FMT_NVCC + !FMT_NVCC && !FMT_ICC_VERSION #endif #if FMT_USE_CONSTEXPR # define FMT_CONSTEXPR constexpr @@ -163,8 +170,16 @@ # endif #endif +#ifndef FMT_INLINE +# if FMT_GCC_VERSION && FMT_USE_CONSTEXPR +# define FMT_INLINE inline __attribute__((always_inline)) +# else +# define FMT_INLINE +# endif +#endif + // Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. -#if defined(__INTEL_COMPILER) || defined(__PGI) || FMT_NVCC +#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC # define FMT_DEPRECATED_ALIAS #else # define FMT_DEPRECATED_ALIAS FMT_DEPRECATED @@ -190,20 +205,15 @@ #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# if FMT_MSC_VER -# define FMT_NO_W4275 __pragma(warning(suppress : 4275)) -# else -# define FMT_NO_W4275 -# endif -# define FMT_CLASS_API FMT_NO_W4275 +# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275) # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) +# define FMT_EXTERN_TEMPLATE_API FMT_API # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) # define FMT_EXTERN_TEMPLATE_API FMT_API # endif -#endif -#ifndef FMT_CLASS_API +#else # define FMT_CLASS_API #endif #ifndef FMT_API @@ -240,9 +250,9 @@ #endif #ifndef FMT_UNICODE -# define FMT_UNICODE 0 +# define FMT_UNICODE !FMT_MSC_VER #endif -#if FMT_UNICODE +#if FMT_UNICODE && FMT_MSC_VER # pragma execution_character_set("utf-8") #endif @@ -274,7 +284,7 @@ namespace internal { // A helper function to suppress bogus "conditional expression is constant" // warnings. -template FMT_CONSTEXPR T const_check(T value) { return value; } +template constexpr T const_check(T value) { return value; } // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template struct void_t_impl { using type = void; }; @@ -284,7 +294,8 @@ FMT_NORETURN FMT_API void assert_fail(const char* file, int line, #ifndef FMT_ASSERT # ifdef NDEBUG -# define FMT_ASSERT(condition, message) +// FMT_ASSERT is not empty to avoid -Werror=empty-body. +# define FMT_ASSERT(condition, message) ((void)0) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ @@ -322,6 +333,19 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int value) { FMT_ASSERT(value >= 0, "negative value"); return static_cast::type>(value); } + +FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5"; + +template constexpr bool is_unicode() { + return FMT_UNICODE || sizeof(Char) != 1 || + (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); +} + +#ifdef __cpp_char8_t +using char8_type = char8_t; +#else +enum char8_type : unsigned char {}; +#endif } // namespace internal template @@ -340,14 +364,13 @@ template class basic_string_view { size_t size_; public: - using char_type FMT_DEPRECATED_ALIAS = Char; using value_type = Char; using iterator = const Char*; - FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} + constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} /** Constructs a string reference object from a C string and a size. */ - FMT_CONSTEXPR basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT + constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT : data_(s), size_(count) {} @@ -357,6 +380,9 @@ template class basic_string_view { the size with ``std::char_traits::length``. \endrst */ +#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr. + FMT_CONSTEXPR +#endif basic_string_view(const Char* s) : data_(s), size_(std::char_traits::length(s)) {} @@ -374,15 +400,15 @@ template class basic_string_view { size_(s.size()) {} /** Returns a pointer to the string data. */ - FMT_CONSTEXPR const Char* data() const { return data_; } + constexpr const Char* data() const { return data_; } /** Returns the string size. */ - FMT_CONSTEXPR size_t size() const { return size_; } + constexpr size_t size() const { return size_; } - FMT_CONSTEXPR iterator begin() const { return data_; } - FMT_CONSTEXPR iterator end() const { return data_ + size_; } + constexpr iterator begin() const { return data_; } + constexpr iterator end() const { return data_ + size_; } - FMT_CONSTEXPR const Char& operator[](size_t pos) const { return data_[pos]; } + constexpr const Char& operator[](size_t pos) const { return data_[pos]; } FMT_CONSTEXPR void remove_prefix(size_t n) { data_ += n; @@ -422,15 +448,15 @@ using string_view = basic_string_view; using wstring_view = basic_string_view; #ifndef __cpp_char8_t -// A UTF-8 code unit type. -enum char8_t : unsigned char {}; +// char8_t is deprecated; use char instead. +using char8_t FMT_DEPRECATED_ALIAS = internal::char8_type; #endif /** Specifies if ``T`` is a character type. Can be specialized by users. */ template struct is_char : std::false_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; -template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; template <> struct is_char : std::true_type {}; @@ -503,8 +529,8 @@ template struct char_t_impl::value>> { }; struct error_handler { - FMT_CONSTEXPR error_handler() = default; - FMT_CONSTEXPR error_handler(const error_handler&) = default; + constexpr error_handler() = default; + constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. FMT_NORETURN FMT_API void on_error(const char* message); @@ -540,22 +566,20 @@ class basic_format_parse_context : private ErrorHandler { using char_type = Char; using iterator = typename basic_string_view::iterator; - explicit FMT_CONSTEXPR basic_format_parse_context( - basic_string_view format_str, ErrorHandler eh = ErrorHandler()) + explicit constexpr basic_format_parse_context( + basic_string_view format_str, ErrorHandler eh = {}) : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} /** Returns an iterator to the beginning of the format string range being parsed. */ - FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT { - return format_str_.begin(); - } + constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); } /** Returns an iterator past the end of the format string range being parsed. */ - FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); } /** Advances the begin iterator to ``it``. */ FMT_CONSTEXPR void advance_to(iterator it) { @@ -567,6 +591,8 @@ class basic_format_parse_context : private ErrorHandler { the next argument index and switches to the automatic indexing. */ FMT_CONSTEXPR int next_arg_id() { + // Don't check if the argument id is valid to avoid overhead and because it + // will be checked during formatting anyway. if (next_arg_id_ >= 0) return next_arg_id_++; on_error("cannot switch from manual to automatic argument indexing"); return 0; @@ -589,7 +615,7 @@ class basic_format_parse_context : private ErrorHandler { ErrorHandler::on_error(message); } - FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; } + constexpr ErrorHandler error_handler() const { return *this; } }; using format_parse_context = basic_format_parse_context; @@ -611,11 +637,6 @@ struct formatter { formatter() = delete; }; -template -struct FMT_DEPRECATED convert_to_int - : bool_constant::value && - std::is_convertible::value> {}; - // Specifies if T has an enabled formatter specialization. A type can be // formattable even if it doesn't have a formatter e.g. via a conversion. template @@ -633,6 +654,7 @@ template class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. + FMT_SUPPRESS_MSC_WARNING(26495) buffer(std::size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} buffer(T* p = nullptr, std::size_t sz = 0, std::size_t cap = 0) FMT_NOEXCEPT @@ -699,8 +721,10 @@ template class buffer { /** Appends data to the end of the buffer. */ template void append(const U* begin, const U* end); - T& operator[](std::size_t index) { return ptr_[index]; } - const T& operator[](std::size_t index) const { return ptr_[index]; } + template T& operator[](I index) { return ptr_[index]; } + template const T& operator[](I index) const { + return ptr_[index]; + } }; // A container-backed buffer. @@ -744,9 +768,71 @@ using has_fallback_formatter = template struct named_arg_base; template struct named_arg; +template struct named_arg_info { + const Char* name; + int id; +}; + +template +struct arg_data { + // args_[0].named_args points to named_args_ to avoid bloating format_args. + T args_[1 + (NUM_ARGS != 0 ? NUM_ARGS : 1)]; + named_arg_info named_args_[NUM_NAMED_ARGS]; + + template + arg_data(const U&... init) : args_{T(named_args_, NUM_NAMED_ARGS), init...} {} + arg_data(const arg_data& other) = delete; + const T* args() const { return args_ + 1; } + named_arg_info* named_args() { return named_args_; } +}; + +template +struct arg_data { + T args_[NUM_ARGS != 0 ? NUM_ARGS : 1]; + + template + FMT_INLINE arg_data(const U&... init) : args_{init...} {} + FMT_INLINE const T* args() const { return args_; } + FMT_INLINE std::nullptr_t named_args() { return nullptr; } +}; + +template +inline void init_named_args(named_arg_info*, int, int) {} + +template +void init_named_args(named_arg_info* named_args, int arg_count, + int named_arg_count, const T&, const Tail&... args) { + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template +void init_named_args(named_arg_info* named_args, int arg_count, + int named_arg_count, const named_arg& arg, + const Tail&... args) { + named_args[named_arg_count++] = {arg.name, arg_count}; + init_named_args(named_args, arg_count + 1, named_arg_count, args...); +} + +template +FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} + +template struct is_named_arg : std::false_type {}; + +template +struct is_named_arg> : std::true_type {}; + +template constexpr size_t count() { return B ? 1 : 0; } +template constexpr size_t count() { + return (B1 ? 1 : 0) + count(); +} + +template constexpr size_t count_named_args() { + return count::value...>(); +} + enum class type { none_type, - named_arg_type, + named_arg_type, // DEPRECATED // Integer types should go first, int_type, uint_type, @@ -777,7 +863,6 @@ struct type_constant : std::integral_constant {}; struct type_constant \ : std::integral_constant {} -FMT_TYPE_CONSTANT(const named_arg_base&, named_arg_type); FMT_TYPE_CONSTANT(int, int_type); FMT_TYPE_CONSTANT(unsigned, uint_type); FMT_TYPE_CONSTANT(long long, long_long_type); @@ -793,13 +878,11 @@ FMT_TYPE_CONSTANT(const Char*, cstring_type); FMT_TYPE_CONSTANT(basic_string_view, string_type); FMT_TYPE_CONSTANT(const void*, pointer_type); -FMT_CONSTEXPR bool is_integral_type(type t) { - FMT_ASSERT(t != type::named_arg_type, "invalid argument type"); +constexpr bool is_integral_type(type t) { return t > type::none_type && t <= type::last_integer_type; } -FMT_CONSTEXPR bool is_arithmetic_type(type t) { - FMT_ASSERT(t != type::named_arg_type, "invalid argument type"); +constexpr bool is_arithmetic_type(type t) { return t > type::none_type && t <= type::last_numeric_type; } @@ -808,10 +891,16 @@ template struct string_value { std::size_t size; }; +template struct named_arg_value { + const named_arg_info* data; + std::size_t size; +}; + template struct custom_value { using parse_context = basic_format_parse_context; const void* value; - void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); + void (*format)(const void* arg, + typename Context::parse_context_type& parse_ctx, Context& ctx); }; // A formatting argument value. @@ -834,28 +923,31 @@ template class value { const void* pointer; string_value string; custom_value custom; - const named_arg_base* named_arg; + const named_arg_base* named_arg; // DEPRECATED + named_arg_value named_args; }; - FMT_CONSTEXPR value(int val = 0) : int_value(val) {} - FMT_CONSTEXPR value(unsigned val) : uint_value(val) {} - value(long long val) : long_long_value(val) {} - value(unsigned long long val) : ulong_long_value(val) {} - value(int128_t val) : int128_value(val) {} - value(uint128_t val) : uint128_value(val) {} - value(float val) : float_value(val) {} - value(double val) : double_value(val) {} - value(long double val) : long_double_value(val) {} - value(bool val) : bool_value(val) {} - value(char_type val) : char_value(val) {} - value(const char_type* val) { string.data = val; } - value(basic_string_view val) { + constexpr FMT_INLINE value(int val = 0) : int_value(val) {} + constexpr FMT_INLINE value(unsigned val) : uint_value(val) {} + FMT_INLINE value(long long val) : long_long_value(val) {} + 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) {} + FMT_INLINE value(long double val) : long_double_value(val) {} + FMT_INLINE value(bool val) : bool_value(val) {} + FMT_INLINE value(char_type val) : char_value(val) {} + FMT_INLINE value(const char_type* val) { string.data = val; } + FMT_INLINE value(basic_string_view val) { string.data = val.data(); string.size = val.size(); } - value(const void* val) : pointer(val) {} + FMT_INLINE value(const void* val) : pointer(val) {} + FMT_INLINE value(const named_arg_info* args, size_t size) + : named_args{args, size} {} - template value(const T& val) { + template FMT_INLINE value(const T& val) { custom.value = &val; // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter` for `format` and @@ -866,14 +958,12 @@ template class value { fallback_formatter>>; } - value(const named_arg_base& val) { named_arg = &val; } - private: // Formats an argument of a custom type, such as a user-defined class. template - static void format_custom_arg( - const void* arg, basic_format_parse_context& parse_ctx, - Context& ctx) { + static void format_custom_arg(const void* arg, + typename Context::parse_context_type& parse_ctx, + Context& ctx) { Formatter f; parse_ctx.advance_to(f.parse(parse_ctx)); ctx.advance_to(f.format(*static_cast(arg), ctx)); @@ -953,6 +1043,14 @@ template struct arg_mapper { static_assert(std::is_same::value, "invalid string type"); return reinterpret_cast(val); } + FMT_CONSTEXPR const char* map(signed char* val) { + const auto* const_val = val; + return map(const_val); + } + FMT_CONSTEXPR const char* map(unsigned char* val) { + const auto* const_val = val; + return map(const_val); + } FMT_CONSTEXPR const void* map(void* val) { return val; } FMT_CONSTEXPR const void* map(const void* val) { return val; } @@ -984,11 +1082,9 @@ template struct arg_mapper { } template - FMT_CONSTEXPR const named_arg_base& map( - const named_arg& val) { - auto arg = make_arg(val.value); - std::memcpy(val.data, &arg, sizeof(arg)); - return val; + FMT_CONSTEXPR auto map(const named_arg& val) + -> decltype(std::declval().map(val.value)) { + return map(val.value); } int map(...) { @@ -1010,8 +1106,9 @@ using mapped_type_constant = enum { packed_arg_bits = 5 }; // Maximum number of arguments with packed types. -enum { max_packed_args = 63 / packed_arg_bits }; +enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; template class arg_map; } // namespace internal @@ -1037,12 +1134,18 @@ template class basic_format_arg { using char_type = typename Context::char_type; + template + friend struct internal::arg_data; + + basic_format_arg(const internal::named_arg_info* args, size_t size) + : value_(args, size) {} + public: class handle { public: explicit handle(internal::custom_value custom) : custom_(custom) {} - void format(basic_format_parse_context& parse_ctx, + void format(typename Context::parse_context_type& parse_ctx, Context& ctx) const { custom_.format(custom_.value, parse_ctx, ctx); } @@ -1051,9 +1154,9 @@ template class basic_format_arg { internal::custom_value custom_; }; - FMT_CONSTEXPR basic_format_arg() : type_(internal::type::none_type) {} + constexpr basic_format_arg() : type_(internal::type::none_type) {} - FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { + constexpr explicit operator bool() const FMT_NOEXCEPT { return type_ != internal::type::none_type; } @@ -1123,41 +1226,6 @@ FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, } namespace internal { -// A map from argument names to their values for named arguments. -template class arg_map { - private: - using char_type = typename Context::char_type; - - struct entry { - basic_string_view name; - basic_format_arg arg; - }; - - entry* map_; - unsigned size_; - - void push_back(value val) { - const auto& named = *val.named_arg; - map_[size_] = {named.name, named.template deserialize()}; - ++size_; - } - - public: - arg_map(const arg_map&) = delete; - void operator=(const arg_map&) = delete; - arg_map() : map_(nullptr), size_(0) {} - void init(const basic_format_args& args); - ~arg_map() { delete[] map_; } - - basic_format_arg find(basic_string_view name) const { - // The list is unsorted, so just return the first matching name. - for (entry *it = map_, *end = map_ + size_; it != end; ++it) { - if (it->name == name) return it->arg; - } - return {}; - } -}; - // A type-erased reference to an std::locale to avoid heavy include. class locale_ref { private: @@ -1199,6 +1267,43 @@ template make_arg(const T& value) { return make_arg(value); } + +template struct is_reference_wrapper : std::false_type {}; + +template +struct is_reference_wrapper> : std::true_type {}; + +class dynamic_arg_list { + // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for + // templates it doesn't complain about inability to deduce single translation + // unit for placing vtable. So storage_node_base is made a fake template. + template struct node { + virtual ~node() = default; + std::unique_ptr> next; + }; + + template struct typed_node : node<> { + T value; + + template + FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} + + template + FMT_CONSTEXPR typed_node(const basic_string_view& arg) + : value(arg.data(), arg.size()) {} + }; + + std::unique_ptr> head_; + + public: + template const T& push(const Arg& arg) { + auto new_node = std::unique_ptr>(new typed_node(arg)); + auto& value = new_node->value; + new_node->next = std::move(head_); + head_ = std::move(new_node); + return value; + } +}; } // namespace internal // Formatting context. @@ -1210,12 +1315,12 @@ template class basic_format_context { private: OutputIt out_; basic_format_args args_; - internal::arg_map map_; internal::locale_ref loc_; public: using iterator = OutputIt; using format_arg = basic_format_arg; + using parse_context_type = basic_format_parse_context; template using formatter_type = formatter; basic_format_context(const basic_format_context&) = delete; @@ -1233,7 +1338,7 @@ template class basic_format_context { // Checks if manual indexing is used and returns the argument with the // specified name. - format_arg arg(basic_string_view name); + format_arg arg(basic_string_view name) { return args_.get(name); } internal::error_handler error_handler() { return {}; } void on_error(const char* message) { error_handler().on_error(message); } @@ -1263,34 +1368,40 @@ using wformat_context = buffer_context; */ template class format_arg_store -#if FMT_GCC_VERSION < 409 +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 // Workaround a GCC template argument substitution bug. : public basic_format_args #endif { private: static const size_t num_args = sizeof...(Args); - static const bool is_packed = num_args < internal::max_packed_args; + static const size_t num_named_args = internal::count_named_args(); + static const bool is_packed = num_args <= internal::max_packed_args; using value_type = conditional_t, basic_format_arg>; - // If the arguments are not packed, add one more element to mark the end. - value_type data_[num_args + (num_args == 0 ? 1 : 0)]; + internal::arg_data + data_; friend class basic_format_args; - public: - static constexpr unsigned long long types = - is_packed ? internal::encode_types() - : internal::is_unpacked_bit | num_args; + static constexpr unsigned long long desc = + (is_packed ? internal::encode_types() + : internal::is_unpacked_bit | num_args) | + (num_named_args != 0 + ? static_cast(internal::has_named_args_bit) + : 0); + public: format_arg_store(const Args&... args) : -#if FMT_GCC_VERSION < 409 +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args(*this), #endif data_{internal::make_arg(args)...} { + internal::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1308,7 +1419,101 @@ inline format_arg_store make_format_args( return {args...}; } -template class dynamic_format_arg_store; +/** + \rst + A dynamic version of `fmt::format_arg_store<>`. + It's equipped with a storage to potentially temporary objects which lifetime + could be shorter than the format arguments object. + + It can be implicitly converted into `~fmt::basic_format_args` for passing + into type-erased formatting functions such as `~fmt::vformat`. + \endrst + */ +template +class dynamic_format_arg_store +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 + // Workaround a GCC template argument substitution bug. + : public basic_format_args +#endif +{ + private: + using char_type = typename Context::char_type; + + template struct need_copy { + static constexpr internal::type mapped_type = + internal::mapped_type_constant::value; + + enum { + value = !(internal::is_reference_wrapper::value || + std::is_same>::value || + std::is_same>::value || + (mapped_type != internal::type::cstring_type && + mapped_type != internal::type::string_type && + mapped_type != internal::type::custom_type && + mapped_type != internal::type::named_arg_type)) + }; + }; + + template + using stored_type = conditional_t::value, + std::basic_string, T>; + + // Storage of basic_format_arg must be contiguous. + std::vector> data_; + + // Storage of arguments not fitting into basic_format_arg must grow + // without relocation because items in data_ refer to it. + internal::dynamic_arg_list dynamic_args_; + + friend class basic_format_args; + + unsigned long long get_types() const { + return internal::is_unpacked_bit | data_.size(); + } + + template void emplace_arg(const T& arg) { + data_.emplace_back(internal::make_arg(arg)); + } + + public: + /** + \rst + Adds an argument into the dynamic store for later passing to a formating + function. + + Note that custom types and string types (but not string views!) are copied + into the store with dynamic memory (in addition to resizing vector). + + **Example**:: + + fmt::dynamic_format_arg_store store; + store.push_back(42); + store.push_back("abc"); + store.push_back(1.5f); + std::string result = fmt::vformat("{} and {} and {}", store); + \endrst + */ + template void push_back(const T& arg) { + static_assert( + !std::is_base_of, T>::value, + "named arguments are not supported yet"); + if (internal::const_check(need_copy::value)) + emplace_arg(dynamic_args_.push>(arg)); + else + emplace_arg(arg); + } + + /** + Adds a reference to the argument into the dynamic store for later passing to + a formating function. + */ + template void push_back(std::reference_wrapper arg) { + static_assert( + need_copy::value, + "objects of built-in types and string views are always copied"); + emplace_arg(arg.get()); + } +}; /** \rst @@ -1326,49 +1531,55 @@ template class basic_format_args { using format_arg = basic_format_arg; private: - // To reduce compiled code size per formatting function call, types of first - // max_packed_args arguments are passed in the types_ field. - unsigned long long types_; + // A descriptor that contains information about formatting arguments. + // If the number of arguments is less or equal to max_packed_args then + // argument types are passed in the descriptor. This reduces binary code size + // per formatting function call. + unsigned long long desc_; union { - // If the number of arguments is less than max_packed_args, the argument - // values are stored in values_, otherwise they are stored in args_. - // This is done to reduce compiled code size as storing larger objects + // If is_packed() returns true then argument values are stored in values_; + // otherwise they are stored in args_. This is done to improve cache + // locality and reduce compiled code size since storing larger objects // may require more code (at least on x86-64) even if the same amount of // data is actually copied to stack. It saves ~10% on the bloat test. const internal::value* values_; const format_arg* args_; }; - bool is_packed() const { return (types_ & internal::is_unpacked_bit) == 0; } + bool is_packed() const { return (desc_ & internal::is_unpacked_bit) == 0; } + bool has_named_args() const { + return (desc_ & internal::has_named_args_bit) != 0; + } internal::type type(int index) const { int shift = index * internal::packed_arg_bits; unsigned int mask = (1 << internal::packed_arg_bits) - 1; - return static_cast((types_ >> shift) & mask); + return static_cast((desc_ >> shift) & mask); } friend class internal::arg_map; - void set_data(const internal::value* values) { values_ = values; } - void set_data(const format_arg* args) { args_ = args; } + basic_format_args(unsigned long long desc, + const internal::value* values) + : desc_(desc), values_(values) {} + basic_format_args(unsigned long long desc, const format_arg* args) + : desc_(desc), args_(args) {} format_arg do_get(int index) const { format_arg arg; if (!is_packed()) { - auto num_args = max_size(); - if (index < num_args) arg = args_[index]; + if (index < max_size()) arg = args_[index]; return arg; } - if (index > internal::max_packed_args) return arg; + if (index >= internal::max_packed_args) return arg; arg.type_ = type(index); if (arg.type_ == internal::type::none_type) return arg; - internal::value& val = arg.value_; - val = values_[index]; + arg.value_ = values_[index]; return arg; } public: - basic_format_args() : types_(0) {} + basic_format_args() : desc_(0) {} /** \rst @@ -1376,10 +1587,8 @@ template class basic_format_args { \endrst */ template - basic_format_args(const format_arg_store& store) - : types_(store.types) { - set_data(store.data_); - } + FMT_INLINE basic_format_args(const format_arg_store& store) + : basic_format_args(store.desc, store.data_.args()) {} /** \rst @@ -1387,10 +1596,8 @@ template class basic_format_args { `~fmt::dynamic_format_arg_store`. \endrst */ - basic_format_args(const dynamic_format_arg_store& store) - : types_(store.get_types()) { - set_data(store.data_.data()); - } + FMT_INLINE basic_format_args(const dynamic_format_arg_store& store) + : basic_format_args(store.get_types(), store.data_.data()) {} /** \rst @@ -1398,22 +1605,31 @@ template class basic_format_args { \endrst */ basic_format_args(const format_arg* args, int count) - : types_(internal::is_unpacked_bit | internal::to_unsigned(count)) { - set_data(args); - } + : basic_format_args( + internal::is_unpacked_bit | internal::to_unsigned(count), args) {} - /** Returns the argument at specified index. */ - format_arg get(int index) const { - format_arg arg = do_get(index); + /** Returns the argument with the specified id. */ + format_arg get(int id) const { + format_arg arg = do_get(id); if (arg.type_ == internal::type::named_arg_type) arg = arg.value_.named_arg->template deserialize(); return arg; } + template format_arg get(basic_string_view name) const { + if (!has_named_args()) return {}; + const auto& named_args = + (is_packed() ? values_[-1] : args_[-1].value_).named_args; + for (size_t i = 0; i < named_args.size; ++i) { + if (named_args.data[i].name == name) return get(named_args.data[i].id); + } + return {}; + } + int max_size() const { unsigned long long max_packed = internal::max_packed_args; return static_cast(is_packed() ? max_packed - : types_ & ~internal::is_unpacked_bit); + : desc_ & ~internal::is_unpacked_bit); } }; @@ -1421,13 +1637,10 @@ template class basic_format_args { // It is a separate type rather than an alias to make symbols readable. struct format_args : basic_format_args { template - format_args(Args&&... args) - : basic_format_args(static_cast(args)...) {} + FMT_INLINE format_args(const Args&... args) : basic_format_args(args...) {} }; struct wformat_args : basic_format_args { - template - wformat_args(Args&&... args) - : basic_format_args(static_cast(args)...) {} + using basic_format_args::basic_format_args; }; template struct is_contiguous : std::false_type {}; @@ -1447,12 +1660,12 @@ struct is_contiguous_back_insert_iterator> : is_contiguous {}; template struct named_arg_base { - basic_string_view name; + const Char* name; // Serialized value. mutable char data[sizeof(basic_format_arg>)]; - named_arg_base(basic_string_view nm) : name(nm) {} + named_arg_base(const Char* nm) : name(nm) {} template basic_format_arg deserialize() const { basic_format_arg arg; @@ -1467,13 +1680,14 @@ template struct named_arg : view, named_arg_base { const T& value; - named_arg(basic_string_view name, const T& val) + named_arg(const Char* name, const T& val) : named_arg_base(name), value(val) {} }; +// Reports a compile-time error if S is not a valid format string. template ::value)> -inline void check_format_string(const S&) { -#if defined(FMT_ENFORCE_COMPILE_STRING) +FMT_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING static_assert(is_compile_string::value, "FMT_ENFORCE_COMPILE_STRING requires all format strings to " "utilize FMT_STRING() or fmt()."); @@ -1482,19 +1696,13 @@ inline void check_format_string(const S&) { template ::value)> void check_format_string(S); -template struct bool_pack; -template -using all_true = - std::is_same, bool_pack>; - template > inline format_arg_store, remove_reference_t...> make_args_checked(const S& format_str, const remove_reference_t&... args) { - static_assert( - all_true<(!std::is_base_of>::value || - !std::is_reference::value)...>::value, - "passing views as lvalues is disallowed"); + static_assert(count<(std::is_base_of>::value && + std::is_reference::value)...>() == 0, + "passing views as lvalues is disallowed"); check_format_string(format_str); return {args...}; } @@ -1509,7 +1717,14 @@ typename buffer_context::iterator vformat_to( buffer& buf, basic_string_view format_str, basic_format_args>> args); +template ::value)> +inline void vprint_mojibake(std::FILE*, basic_string_view, const Args&) {} + FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); +#ifndef _WIN32 +inline void vprint_mojibake(std::FILE*, string_view, format_args) {} +#endif } // namespace internal /** @@ -1522,16 +1737,12 @@ FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); \endrst */ -template > -inline internal::named_arg arg(const S& name, const T& arg) { - static_assert(internal::is_string::value, ""); +template +inline internal::named_arg arg(const Char* name, const T& arg) { + static_assert(!internal::is_named_arg(), "nested named arguments"); return {name, arg}; } -// Disable nested named arguments, e.g. ``arg("a", arg("b", 42))``. -template -void arg(S, internal::named_arg) = delete; - /** Formats a string and writes the output to ``out``. */ // GCC 8 and earlier cannot handle std::back_insert_iterator with // vformat_to(...) overload, so SFINAE on iterator type instead. @@ -1541,8 +1752,8 @@ template , OutputIt vformat_to( OutputIt out, const S& format_str, basic_format_args>> args) { - using container = remove_reference_t; - internal::container_buffer buf((internal::get_container(out))); + auto& c = internal::get_container(out); + internal::container_buffer> buf(c); internal::vformat_to(buf, to_string_view(format_str), args); return out; } @@ -1558,7 +1769,7 @@ inline std::back_insert_iterator format_to( } template > -inline std::basic_string vformat( +FMT_INLINE std::basic_string vformat( const S& format_str, basic_format_args>> args) { return internal::vformat(to_string_view(format_str), args); @@ -1577,10 +1788,9 @@ inline std::basic_string vformat( // Pass char_t as a default template parameter instead of using // std::basic_string> to reduce the symbol size. template > -inline std::basic_string format(const S& format_str, Args&&... args) { - return internal::vformat( - to_string_view(format_str), - internal::make_args_checked(format_str, args...)); +FMT_INLINE std::basic_string format(const S& format_str, Args&&... args) { + const auto& vargs = internal::make_args_checked(format_str, args...); + return internal::vformat(to_string_view(format_str), vargs); } FMT_API void vprint(string_view, format_args); @@ -1597,17 +1807,12 @@ FMT_API void vprint(std::FILE*, string_view, format_args); fmt::print(stderr, "Don't {}!", "panic"); \endrst */ -template ::value)> +template > inline void print(std::FILE* f, const S& format_str, Args&&... args) { -#if !defined(_WIN32) || FMT_UNICODE - vprint(f, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#else - internal::vprint_mojibake( - f, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#endif + const auto& vargs = internal::make_args_checked(format_str, args...); + return internal::is_unicode() + ? vprint(f, to_string_view(format_str), vargs) + : internal::vprint_mojibake(f, to_string_view(format_str), vargs); } /** @@ -1621,192 +1826,14 @@ inline void print(std::FILE* f, const S& format_str, Args&&... args) { fmt::print("Elapsed time: {0:.2f} seconds", 1.23); \endrst */ -template ::value)> +template > inline void print(const S& format_str, Args&&... args) { -#if !defined(_WIN32) || FMT_UNICODE - vprint(to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#else - internal::vprint_mojibake( - stdout, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); -#endif + const auto& vargs = internal::make_args_checked(format_str, args...); + return internal::is_unicode() + ? vprint(to_string_view(format_str), vargs) + : internal::vprint_mojibake(stdout, to_string_view(format_str), + vargs); } - -namespace internal { - -template struct is_string_view : std::false_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template -struct is_string_view, Char> : std::true_type {}; - -template struct is_reference_wrapper : std::false_type {}; - -template -struct is_reference_wrapper> : std::true_type {}; - -class dyn_arg_storage { - // Workaround for clang's -Wweak-vtables. Unlike for regular classes, for - // templates it doesn't complain about inability to deduce single translation - // unit for placing vtable. So storage_node_base is made a fake template. - - template struct storage_node_base { - using owning_ptr = std::unique_ptr>; - virtual ~storage_node_base() = default; - owning_ptr next; - }; - - template struct storage_node : storage_node_base<> { - T value; - template - FMT_CONSTEXPR storage_node(const Arg& arg, owning_ptr&& next) : value{arg} { - // Must be initialised after value_ - this->next = std::move(next); - } - - template - FMT_CONSTEXPR storage_node(const basic_string_view& arg, - owning_ptr&& next) - : value{arg.data(), arg.size()} { - // Must be initialised after value - this->next = std::move(next); - } - }; - - storage_node_base<>::owning_ptr head_{nullptr}; - - public: - dyn_arg_storage() = default; - dyn_arg_storage(const dyn_arg_storage&) = delete; - dyn_arg_storage(dyn_arg_storage&&) = default; - - dyn_arg_storage& operator=(const dyn_arg_storage&) = delete; - dyn_arg_storage& operator=(dyn_arg_storage&&) = default; - - template const T& push(const Arg& arg) { - auto node = new storage_node{arg, std::move(head_)}; - head_.reset(node); - return node->value; - } -}; - -} // namespace internal - -/** - \rst - A dynamic version of `fmt::format_arg_store<>`. - It's equipped with a storage to potentially temporary objects which lifetime - could be shorter than the format arguments object. - - It can be implicitly converted into `~fmt::basic_format_args` for passing - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template -class dynamic_format_arg_store -#if FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args -#endif -{ - private: - using char_type = typename Context::char_type; - - template struct need_dyn_copy { - static constexpr internal::type mapped_type = - internal::mapped_type_constant::value; -// static_assert( -// mapped_type != internal::type::named_arg_type, -// "Bug indicator. Named arguments must be processed separately"); - - using type = std::integral_constant< - bool, !(internal::is_reference_wrapper::value || - internal::is_string_view::value || - (mapped_type != internal::type::cstring_type && - mapped_type != internal::type::string_type && - mapped_type != internal::type::custom_type && - mapped_type != internal::type::named_arg_type))>; - }; - - template - using stored_type = conditional_t::value, - std::basic_string, T>; - - // Storage of basic_format_arg must be contiguous - // Required by basic_format_args::args_ which is just a pointer. - std::vector> data_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - - internal::dyn_arg_storage storage_; - - friend class basic_format_args; - - unsigned long long get_types() const { - return internal::is_unpacked_bit | data_.size(); - } - - template void emplace_arg(const T& arg) { - data_.emplace_back(internal::make_arg(arg)); - } - - public: - dynamic_format_arg_store() = default; - ~dynamic_format_arg_store() = default; - - dynamic_format_arg_store(const dynamic_format_arg_store&) = delete; - dynamic_format_arg_store& operator=(const dynamic_format_arg_store&) = delete; - - dynamic_format_arg_store(dynamic_format_arg_store&&) = default; - dynamic_format_arg_store& operator=(dynamic_format_arg_store&&) = default; - - /** - \rst - Adds an argument into the dynamic store for later passing to a formating - function. - - Note that custom types and string types (but not string views!) are copied - into the store with dynamic memory (in addition to resizing vector). - - **Example**:: - - #include - fmt::dynamic_format_arg_store store; - store.push_back(42); - store.push_back("abc1"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template void push_back(const T& arg) { - static_assert( - !std::is_base_of, T>::value, - "Named arguments are not supported yet"); - if (internal::const_check(need_dyn_copy::type::value)) - emplace_arg(storage_.push>(arg)); - else - emplace_arg(arg); - } - - /** - \rst - Adds an argument into the dynamic store for later passing to a formating - function without copying into type-erasing list. - \endrst - */ - template void push_back(std::reference_wrapper arg) { - static_assert( - need_dyn_copy::type::value, - "Primitive types and string views directly supported by " - "basic_format_arg. Passing them by reference is not allowed"); - emplace_arg(arg.get()); - } -}; FMT_END_NAMESPACE #endif // FMT_CORE_H_ diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 5d294bc7..71ee20f5 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -22,8 +22,14 @@ #endif #ifdef _WIN32 +# if defined(NOMINMAX) +# include +# else +# define NOMINMAX +# include +# undef NOMINMAX +# endif # include -# include #endif #ifdef _MSC_VER @@ -317,6 +323,8 @@ const char basic_data::background_color[] = "\x1b[48;2;"; template const char basic_data::reset_color[] = "\x1b[0m"; template const wchar_t basic_data::wreset_color[] = L"\x1b[0m"; template const char basic_data::signs[] = {0, '-', '+', ' '}; +template +const char basic_data::padding_shifts[] = {31, 31, 0, 1, 0}; template struct bits { static FMT_CONSTEXPR_DECL const int value = @@ -507,20 +515,23 @@ class bigint { basic_memory_buffer bigits_; int exp_; + bigit operator[](int index) const { return bigits_[to_unsigned(index)]; } + bigit& operator[](int index) { return bigits_[to_unsigned(index)]; } + static FMT_CONSTEXPR_DECL const int bigit_bits = bits::value; friend struct formatter; void subtract_bigits(int index, bigit other, bigit& borrow) { - auto result = static_cast(bigits_[index]) - other - borrow; - bigits_[index] = static_cast(result); + auto result = static_cast((*this)[index]) - other - borrow; + (*this)[index] = static_cast(result); borrow = static_cast(result >> (bigit_bits * 2 - 1)); } void remove_leading_zeros() { int num_bigits = static_cast(bigits_.size()) - 1; - while (num_bigits > 0 && bigits_[num_bigits] == 0) --num_bigits; - bigits_.resize(num_bigits + 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. @@ -591,7 +602,7 @@ class bigint { int num_bigits() const { return static_cast(bigits_.size()) + exp_; } - bigint& operator<<=(int shift) { + FMT_NOINLINE bigint& operator<<=(int shift) { assert(shift >= 0); exp_ += shift / bigit_bits; shift %= bigit_bits; @@ -621,7 +632,7 @@ class bigint { int end = i - j; if (end < 0) end = 0; for (; i >= end; --i, --j) { - bigit lhs_bigit = lhs.bigits_[i], rhs_bigit = rhs.bigits_[j]; + bigit lhs_bigit = lhs[i], rhs_bigit = rhs[j]; if (lhs_bigit != rhs_bigit) return lhs_bigit > rhs_bigit ? 1 : -1; } if (i != j) return i > j ? 1 : -1; @@ -636,7 +647,7 @@ class bigint { if (max_lhs_bigits + 1 < num_rhs_bigits) return -1; if (max_lhs_bigits > num_rhs_bigits) return 1; auto get_bigit = [](const bigint& n, int i) -> bigit { - return i >= n.exp_ && i < n.num_bigits() ? n.bigits_[i - n.exp_] : 0; + return i >= n.exp_ && i < n.num_bigits() ? n[i - n.exp_] : 0; }; double_bigit borrow = 0; int min_exp = (std::min)((std::min)(lhs1.exp_, lhs2.exp_), rhs.exp_); @@ -676,7 +687,7 @@ class bigint { basic_memory_buffer n(std::move(bigits_)); int num_bigits = static_cast(bigits_.size()); int num_result_bigits = 2 * num_bigits; - bigits_.resize(num_result_bigits); + bigits_.resize(to_unsigned(num_result_bigits)); using accumulator_t = conditional_t; auto sum = accumulator_t(); for (int bigit_index = 0; bigit_index < num_bigits; ++bigit_index) { @@ -686,7 +697,7 @@ class bigint { // Most terms are multiplied twice which can be optimized in the future. sum += static_cast(n[i]) * n[j]; } - bigits_[bigit_index] = static_cast(sum); + (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; // Compute the carry. } // Do the same for the top half. @@ -694,7 +705,7 @@ class bigint { ++bigit_index) { for (int j = num_bigits - 1, i = bigit_index - j; i < num_bigits;) sum += static_cast(n[i++]) * n[j--]; - bigits_[bigit_index] = static_cast(sum); + (*this)[bigit_index] = static_cast(sum); sum >>= bits::value; } --num_result_bigits; @@ -708,11 +719,11 @@ class bigint { FMT_ASSERT(this != &divisor, ""); if (compare(*this, divisor) < 0) return 0; int num_bigits = static_cast(bigits_.size()); - FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1] != 0, ""); + FMT_ASSERT(divisor.bigits_[divisor.bigits_.size() - 1u] != 0, ""); int exp_difference = exp_ - divisor.exp_; if (exp_difference > 0) { // Align bigints by adding trailing zeros to simplify subtraction. - bigits_.resize(num_bigits + exp_difference); + 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]; std::uninitialized_fill_n(bigits_.data(), exp_difference, 0); @@ -1023,7 +1034,7 @@ void fallback_format(Double d, buffer& buf, int& exp10) { if (result > 0 || (result == 0 && (digit % 2) != 0)) ++data[num_digits - 1]; } - buf.resize(num_digits); + buf.resize(to_unsigned(num_digits)); exp10 -= num_digits - 1; return; } @@ -1142,7 +1153,7 @@ int snprintf_float(T value, int precision, float_specs specs, for (;;) { auto begin = buf.data() + offset; auto capacity = buf.capacity() - offset; -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION +#ifdef FMT_FUZZ if (precision > 100000) throw std::runtime_error( "fuzz mode - avoid large allocation inside snprintf"); @@ -1157,7 +1168,7 @@ int snprintf_float(T value, int precision, float_specs specs, buf.reserve(buf.capacity() + 1); // The buffer will grow exponentially. continue; } - unsigned size = to_unsigned(result); + auto size = to_unsigned(result); // Size equal to capacity means that the last character was truncated. if (size >= capacity) { buf.reserve(size + offset + 1); // Add 1 for the terminating '\0'. @@ -1175,7 +1186,7 @@ int snprintf_float(T value, int precision, float_specs specs, --p; } while (is_digit(*p)); int fraction_size = static_cast(end - p - 1); - std::memmove(p, p + 1, fraction_size); + std::memmove(p, p + 1, to_unsigned(fraction_size)); buf.resize(size - 1); return -fraction_size; } @@ -1204,9 +1215,9 @@ int snprintf_float(T value, int precision, float_specs specs, while (*fraction_end == '0') --fraction_end; // Move the fractional part left to get rid of the decimal point. fraction_size = static_cast(fraction_end - begin - 1); - std::memmove(begin + 1, begin + 2, fraction_size); + std::memmove(begin + 1, begin + 2, to_unsigned(fraction_size)); } - buf.resize(fraction_size + offset + 1); + buf.resize(to_unsigned(fraction_size) + offset + 1); return exp - fraction_size; } } @@ -1277,7 +1288,7 @@ template <> struct formatter { auto out = ctx.out(); bool first = true; for (auto i = n.bigits_.size(); i > 0; --i) { - auto value = n.bigits_[i - 1]; + auto value = n.bigits_[i - 1u]; if (first) { out = format_to(out, "{:x}", value); first = false; @@ -1313,7 +1324,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { } if (auto num_chars_left = s.data() + s.size() - p) { char buf[2 * block_size - 1] = {}; - memcpy(buf, p, num_chars_left); + memcpy(buf, p, to_unsigned(num_chars_left)); p = buf; do { p = transcode(p); @@ -1368,7 +1379,7 @@ FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { if (!WriteConsoleW(reinterpret_cast(_get_osfhandle(fd)), u16.c_str(), static_cast(u16.size()), &written, nullptr)) { - throw format_error("failed to write to console"); + FMT_THROW(format_error("failed to write to console")); } return; } diff --git a/include/fmt/format.h b/include/fmt/format.h index 5a479732..a087ca31 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -43,6 +43,10 @@ #include "core.h" +#ifdef FMT_DEPRECATED_INCLUDE_OS +# include "os.h" +#endif + #ifdef __INTEL_COMPILER # define FMT_ICC_VERSION __INTEL_COMPILER #elif defined(__ICL) @@ -63,6 +67,12 @@ # define FMT_HAS_BUILTIN(x) 0 #endif +#if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_NOINLINE __attribute__((noinline)) +#else +# define FMT_NOINLINE +#endif + #if __cplusplus == 201103L || __cplusplus == 201402L # if defined(__clang__) # define FMT_FALLTHROUGH [[clang::fallthrough]] @@ -129,8 +139,7 @@ FMT_END_NAMESPACE // support UDL templates and GCC >= 9 warns about them. # if FMT_USE_USER_DEFINED_LITERALS && FMT_ICC_VERSION == 0 && \ FMT_CUDA_VERSION == 0 && \ - ((FMT_GCC_VERSION >= 604 && FMT_GCC_VERSION <= 900 && \ - __cplusplus >= 201402L) || \ + ((FMT_GCC_VERSION >= 604 && __cplusplus >= 201402L) || \ FMT_CLANG_VERSION >= 304) # define FMT_USE_UDL_TEMPLATE 1 # else @@ -138,6 +147,18 @@ FMT_END_NAMESPACE # endif #endif +#ifndef FMT_USE_FLOAT +# define FMT_USE_FLOAT 1 +#endif + +#ifndef FMT_USE_DOUBLE +# define FMT_USE_DOUBLE 1 +#endif + +#ifndef FMT_USE_LONG_DOUBLE +# define FMT_USE_LONG_DOUBLE 1 +#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 @@ -167,7 +188,7 @@ inline uint32_t clz(uint32_t x) { // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. -# pragma warning(suppress : 6102) + FMT_SUPPRESS_MSC_WARNING(6102) return 31 - r; } # define FMT_BUILTIN_CLZ(n) internal::clz(n) @@ -192,7 +213,7 @@ inline uint32_t clzll(uint64_t x) { // Static analysis complains about using uninitialized data // "r", but the only way that can happen is if "x" is 0, // which the callers guarantee to not happen. -# pragma warning(suppress : 6102) + FMT_SUPPRESS_MSC_WARNING(6102) return 63 - r; } # define FMT_BUILTIN_CLZLL(n) internal::clzll(n) @@ -205,11 +226,6 @@ FMT_END_NAMESPACE # define FMT_NUMERIC_ALIGN 1 #endif -// Enable the deprecated percent specifier. -#ifndef FMT_DEPRECATED_PERCENT -# define FMT_DEPRECATED_PERCENT 0 -#endif - FMT_BEGIN_NAMESPACE namespace internal { @@ -327,7 +343,7 @@ template inline T* make_checked(T* p, std::size_t) { return p; } template ::value)> inline checked_ptr reserve( - std::back_insert_iterator& it, std::size_t n) { + std::back_insert_iterator it, std::size_t n) { Container& c = get_container(it); std::size_t size = c.size(); c.resize(size + n); @@ -339,6 +355,18 @@ inline Iterator& reserve(Iterator& it, std::size_t) { return it; } +template ::value)> +inline std::back_insert_iterator base_iterator( + std::back_insert_iterator& it, + checked_ptr) { + return it; +} + +template +inline Iterator base_iterator(Iterator, Iterator it) { + return it; +} + // An output iterator that counts the number of objects written to it and // discards them. class counting_iterator { @@ -478,7 +506,7 @@ inline size_t count_code_points(basic_string_view s) { return num_code_points; } -inline size_t count_code_points(basic_string_view s) { +inline size_t count_code_points(basic_string_view s) { return count_code_points(basic_string_view( reinterpret_cast(s.data()), s.size())); } @@ -490,8 +518,8 @@ inline size_t code_point_index(basic_string_view s, size_t n) { } // Calculates the index of the nth code point in a UTF-8 string. -inline size_t code_point_index(basic_string_view s, size_t n) { - const char8_t* data = s.data(); +inline size_t code_point_index(basic_string_view s, size_t n) { + const char8_type* data = s.data(); size_t num_code_points = 0; for (size_t i = 0, size = s.size(); i != size; ++i) { if ((data[i] & 0xc0) != 0x80 && ++num_code_points > n) { @@ -501,13 +529,13 @@ inline size_t code_point_index(basic_string_view s, size_t n) { return s.size(); } -inline char8_t to_char8_t(char c) { return static_cast(c); } +inline char8_type to_char8_t(char c) { return static_cast(c); } template using needs_conversion = bool_constant< std::is_same::value_type, char>::value && - std::is_same::value>; + std::is_same::value>; template ::value)> @@ -551,20 +579,22 @@ class buffer_range : public internal::output_range< : internal::output_range(std::back_inserter(buf)) {} }; -class FMT_DEPRECATED u8string_view : public basic_string_view { +class FMT_DEPRECATED u8string_view + : public basic_string_view { public: u8string_view(const char* s) - : basic_string_view(reinterpret_cast(s)) {} + : basic_string_view( + reinterpret_cast(s)) {} u8string_view(const char* s, size_t count) FMT_NOEXCEPT - : basic_string_view(reinterpret_cast(s), count) { - } + : basic_string_view( + reinterpret_cast(s), count) {} }; #if FMT_USE_USER_DEFINED_LITERALS inline namespace literals { -FMT_DEPRECATED inline basic_string_view operator"" _u(const char* s, - std::size_t n) { - return {reinterpret_cast(s), n}; +FMT_DEPRECATED inline basic_string_view operator"" _u( + const char* s, std::size_t n) { + return {reinterpret_cast(s), n}; } } // namespace literals #endif @@ -604,14 +634,17 @@ enum { inline_buffer_size = 500 }; */ template > -class basic_memory_buffer : private Allocator, public internal::buffer { +class basic_memory_buffer : public internal::buffer { private: T store_[SIZE]; + // Don't inherit from Allocator avoid generating type_info for it. + Allocator alloc_; + // Deallocate memory allocated by the buffer. void deallocate() { T* data = this->data(); - if (data != store_) Allocator::deallocate(data, this->capacity()); + if (data != store_) alloc_.deallocate(data, this->capacity()); } protected: @@ -622,7 +655,7 @@ class basic_memory_buffer : private Allocator, public internal::buffer { using const_reference = const T&; explicit basic_memory_buffer(const Allocator& alloc = Allocator()) - : Allocator(alloc) { + : alloc_(alloc) { this->set(store_, SIZE); } ~basic_memory_buffer() FMT_OVERRIDE { deallocate(); } @@ -630,8 +663,7 @@ class basic_memory_buffer : private Allocator, public internal::buffer { private: // Move data from other to this buffer. void move(basic_memory_buffer& other) { - Allocator &this_alloc = *this, &other_alloc = other; - this_alloc = std::move(other_alloc); + alloc_ = std::move(other.alloc_); T* data = other.data(); std::size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { @@ -669,19 +701,20 @@ class basic_memory_buffer : private Allocator, public internal::buffer { } // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return *this; } + Allocator get_allocator() const { return alloc_; } }; template void basic_memory_buffer::grow(std::size_t size) { -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - if (size > 1000) throw std::runtime_error("fuzz mode - won't grow that much"); +#ifdef FMT_FUZZ + if (size > 5000) throw std::runtime_error("fuzz mode - won't grow that much"); #endif std::size_t old_capacity = this->capacity(); std::size_t new_capacity = old_capacity + old_capacity / 2; if (size > new_capacity) new_capacity = size; T* old_data = this->data(); - T* new_data = std::allocator_traits::allocate(*this, new_capacity); + T* new_data = + std::allocator_traits::allocate(alloc_, new_capacity); // The following code doesn't throw, so the raw pointer above doesn't leak. std::uninitialized_copy(old_data, old_data + this->size(), internal::make_checked(new_data, new_capacity)); @@ -689,7 +722,7 @@ void basic_memory_buffer::grow(std::size_t size) { // deallocate must not throw according to the standard, but even if it does, // the buffer already uses the new storage and will deallocate it in // destructor. - if (old_data != store_) Allocator::deallocate(old_data, old_capacity); + if (old_data != store_) alloc_.deallocate(old_data, old_capacity); } using memory_buffer = basic_memory_buffer; @@ -722,6 +755,13 @@ FMT_CONSTEXPR bool is_negative(T) { return false; } +template ::value)> +FMT_CONSTEXPR bool is_supported_floating_point(T) { + return (std::is_same::value && FMT_USE_FLOAT) || + (std::is_same::value && FMT_USE_DOUBLE) || + (std::is_same::value && FMT_USE_LONG_DOUBLE); +} + // Smallest of uint32_t, uint64_t, uint128_t that is large enough to // represent all values of T. template @@ -743,6 +783,7 @@ template struct FMT_EXTERN_TEMPLATE_API basic_data { static const char reset_color[5]; static const wchar_t wreset_color[5]; static const char signs[]; + static const char padding_shifts[5]; }; FMT_EXTERN template struct basic_data; @@ -1040,7 +1081,6 @@ struct float_specs { sign_t sign : 8; bool upper : 1; bool locale : 1; - bool percent : 1; bool binary32 : 1; bool use_grisu : 1; bool showpoint : 1; @@ -1103,8 +1143,8 @@ template class float_writer { *it++ = static_cast('0'); return it; } -#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION - if (num_zeros > 1000) +#ifdef FMT_FUZZ + if (num_zeros > 5000) throw std::runtime_error("fuzz mode - avoiding excessive cpu use"); #endif it = std::fill_n(it, num_zeros, static_cast('0')); @@ -1131,9 +1171,11 @@ template class float_writer { // 1234e-6 -> 0.001234 *it++ = static_cast('0'); int num_zeros = -full_exp; - if (specs_.precision >= 0 && specs_.precision < num_zeros) - num_zeros = specs_.precision; int num_digits = num_digits_; + if (num_digits == 0 && specs_.precision >= 0 && + specs_.precision < num_zeros) { + num_zeros = specs_.precision; + } // Remove trailing zeros. if (!specs_.showpoint) while (num_digits > 0 && digits_[num_digits - 1] == '0') --num_digits; @@ -1165,11 +1207,10 @@ template class float_writer { } size_t size() const { return size_; } - size_t width() const { return size(); } - template void operator()(It&& it) { + template It operator()(It it) const { if (specs_.sign) *it++ = static_cast(data::signs[specs_.sign]); - it = prettify(it); + return prettify(it); } }; @@ -1203,6 +1244,7 @@ FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) { handler.on_oct(); break; case 'n': + case 'L': handler.on_num(); break; default: @@ -1240,12 +1282,6 @@ FMT_CONSTEXPR float_specs parse_float_type_spec( result.format = float_format::fixed; result.showpoint |= specs.precision != 0; break; -#if FMT_DEPRECATED_PERCENT - case '%': - result.format = float_format::fixed; - result.percent = true; - break; -#endif case 'A': result.upper = true; FMT_FALLTHROUGH; @@ -1253,6 +1289,7 @@ FMT_CONSTEXPR float_specs parse_float_type_spec( result.format = float_format::hex; break; case 'n': + case 'L': result.locale = true; break; default: @@ -1332,46 +1369,81 @@ class cstring_type_checker : public ErrorHandler { FMT_CONSTEXPR void on_pointer() {} }; -template -void arg_map::init(const basic_format_args& args) { - if (map_) return; - map_ = new entry[internal::to_unsigned(args.max_size())]; - if (args.is_packed()) { - for (int i = 0;; ++i) { - internal::type arg_type = args.type(i); - if (arg_type == internal::type::none_type) return; - if (arg_type == internal::type::named_arg_type) - push_back(args.values_[i]); - } - } - for (int i = 0, n = args.max_size(); i < n; ++i) { - auto type = args.args_[i].type_; - if (type == internal::type::named_arg_type) push_back(args.args_[i].value_); - } -} - -template struct nonfinite_writer { - sign_t sign; - const char* str; - static constexpr size_t str_size = 3; - - size_t size() const { return str_size + (sign ? 1 : 0); } - size_t width() const { return size(); } - - template void operator()(It&& it) const { - if (sign) *it++ = static_cast(data::signs[sign]); - it = copy_str(str, str + str_size, it); - } -}; - template -OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { +FMT_NOINLINE OutputIt fill(OutputIt it, size_t n, const fill_t& fill) { auto fill_size = fill.size(); if (fill_size == 1) return std::fill_n(it, n, fill[0]); for (size_t i = 0; i < n; ++i) it = std::copy_n(fill.data(), fill_size, it); return it; } +// Writes the output of f, padded according to format specifications in specs. +// size: output size in code units. +// width: output display width in (terminal) column positions. +template +inline OutputIt write_padded(OutputIt out, + const basic_format_specs& specs, size_t size, + size_t width, const F& f) { + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + size_t left_padding = padding >> data::padding_shifts[specs.align]; + auto it = reserve(out, size + padding * specs.fill.size()); + it = fill(it, left_padding, specs.fill); + it = f(it); + it = fill(it, padding - left_padding, specs.fill); + return base_iterator(out, it); +} + +template +inline OutputIt write_padded(OutputIt out, + const basic_format_specs& specs, size_t size, + const F& f) { + return write_padded(out, specs, size, size, f); +} + +// Data for write_int that doesn't depend on output iterator type. It is used to +// avoid template code bloat. +template struct write_int_data { + std::size_t size; + std::size_t padding; + Char fill; + + write_int_data(int num_digits, string_view prefix, + basic_format_specs& specs) + : size(prefix.size() + to_unsigned(num_digits)), + padding(0), + fill(specs.fill[0]) { + if (specs.align == align::numeric) { + auto width = to_unsigned(specs.width); + if (width > size) { + padding = width - size; + size = width; + } + } else if (specs.precision > num_digits) { + size = prefix.size() + to_unsigned(specs.precision); + padding = to_unsigned(specs.precision - num_digits); + fill = static_cast('0'); + } + if (specs.align == align::none) specs.align = align::right; + } +}; + +// Writes an integer in the format +// +// where are written by f(it). +template +OutputIt write_int(OutputIt out, int num_digits, string_view prefix, + basic_format_specs specs, F f) { + auto data = write_int_data(num_digits, prefix, specs); + using iterator = remove_reference_t; + return write_padded(out, specs, data.size, [=](iterator it) { + if (prefix.size() != 0) + it = copy_str(prefix.begin(), prefix.end(), it); + it = std::fill_n(it, data.padding, data.fill); + return f(it); + }); +} + // This template provides operations for formatting and writing data into a // character range. template class basic_writer { @@ -1390,47 +1462,6 @@ template class basic_writer { return internal::reserve(out_, n); } - template struct padded_int_writer { - size_t size_; - string_view prefix; - char_type fill; - std::size_t padding; - F f; - - size_t size() const { return size_; } - size_t width() const { return size_; } - - template void operator()(It&& it) const { - if (prefix.size() != 0) - it = copy_str(prefix.begin(), prefix.end(), it); - it = std::fill_n(it, padding, fill); - f(it); - } - }; - - // Writes an integer in the format - // - // where are written by f(it). - template - void write_int(int num_digits, string_view prefix, format_specs specs, F f) { - std::size_t size = prefix.size() + to_unsigned(num_digits); - char_type fill = specs.fill[0]; - std::size_t padding = 0; - if (specs.align == align::numeric) { - auto unsiged_width = to_unsigned(specs.width); - if (unsiged_width > size) { - padding = unsiged_width - size; - size = unsiged_width; - } - } else if (specs.precision > num_digits) { - size = prefix.size() + to_unsigned(specs.precision); - padding = to_unsigned(specs.precision - num_digits); - fill = static_cast('0'); - } - if (specs.align == align::none) specs.align = align::right; - write_padded(specs, padded_int_writer{size, prefix, fill, padding, f}); - } - // Writes a decimal integer. template void write_decimal(Int value) { auto abs_value = static_cast>(value); @@ -1444,22 +1475,23 @@ template class basic_writer { } // The handle_int_type_spec handler that writes an integer. - template struct int_writer { - using unsigned_type = uint32_or_64_or_128_t; - + template struct int_writer { basic_writer& writer; - const Specs& specs; - unsigned_type abs_value; + const basic_format_specs& specs; + UInt abs_value; char prefix[4]; unsigned prefix_size; string_view get_prefix() const { return string_view(prefix, prefix_size); } - int_writer(basic_writer& w, Int value, const Specs& s) + template + int_writer(basic_writer& w, Int value, + const basic_format_specs& s) : writer(w), specs(s), - abs_value(static_cast(value)), + abs_value(static_cast(value)), prefix_size(0) { + static_assert(std::is_same, UInt>::value, ""); if (is_negative(value)) { prefix[0] = '-'; ++prefix_size; @@ -1470,58 +1502,40 @@ template class basic_writer { } } - struct dec_writer { - unsigned_type abs_value; - int num_digits; - - template void operator()(It&& it) const { - it = internal::format_decimal(it, abs_value, num_digits); - } - }; - void on_dec() { - int num_digits = count_digits(abs_value); - writer.write_int(num_digits, get_prefix(), specs, - dec_writer{abs_value, num_digits}); + auto num_digits = count_digits(abs_value); + writer.out_ = internal::write_int(writer.out_, num_digits, get_prefix(), + specs, [=](reserve_iterator it) { + return format_decimal( + it, abs_value, num_digits); + }); } - struct hex_writer { - int_writer& self; - int num_digits; - - template void operator()(It&& it) const { - it = format_uint<4, char_type>(it, self.abs_value, num_digits, - self.specs.type != 'x'); - } - }; - void on_hex() { if (specs.alt) { prefix[prefix_size++] = '0'; prefix[prefix_size++] = specs.type; } int num_digits = count_digits<4>(abs_value); - writer.write_int(num_digits, get_prefix(), specs, - hex_writer{*this, num_digits}); + writer.out_ = internal::write_int(writer.out_, num_digits, get_prefix(), + specs, [=](reserve_iterator it) { + return format_uint<4, char_type>( + it, abs_value, num_digits, + specs.type != 'x'); + }); } - template struct bin_writer { - unsigned_type abs_value; - int num_digits; - - template void operator()(It&& it) const { - it = format_uint(it, abs_value, num_digits); - } - }; - void on_bin() { if (specs.alt) { prefix[prefix_size++] = '0'; prefix[prefix_size++] = static_cast(specs.type); } int num_digits = count_digits<1>(abs_value); - writer.write_int(num_digits, get_prefix(), specs, - bin_writer<1>{abs_value, num_digits}); + writer.out_ = internal::write_int(writer.out_, num_digits, get_prefix(), + specs, [=](reserve_iterator it) { + return format_uint<1, char_type>( + it, abs_value, num_digits); + }); } void on_oct() { @@ -1531,25 +1545,28 @@ template class basic_writer { // is not greater than the number of digits. prefix[prefix_size++] = '0'; } - writer.write_int(num_digits, get_prefix(), specs, - bin_writer<3>{abs_value, num_digits}); + writer.out_ = internal::write_int(writer.out_, num_digits, get_prefix(), + specs, [=](reserve_iterator it) { + return format_uint<3, char_type>( + it, abs_value, num_digits); + }); } enum { sep_size = 1 }; struct num_writer { - unsigned_type abs_value; + UInt abs_value; int size; const std::string& groups; char_type sep; - template void operator()(It&& it) const { + template It operator()(It it) const { basic_string_view s(&sep, sep_size); // Index of a decimal digit with the least significant digit having // index 0. int digit_index = 0; std::string::const_iterator group = groups.cbegin(); - it = format_decimal( + return format_decimal( it, abs_value, size, [this, s, &group, &digit_index](char_type*& buffer) { if (*group <= 0 || ++digit_index % *group != 0 || @@ -1582,8 +1599,9 @@ template class basic_writer { } if (group == groups.cend()) size += sep_size * ((num_digits - 1) / groups.back()); - writer.write_int(size, get_prefix(), specs, - num_writer{abs_value, size, groups, sep}); + writer.out_ = + internal::write_int(writer.out_, size, get_prefix(), specs, + num_writer{abs_value, size, groups, sep}); } FMT_NORETURN void on_error() { @@ -1591,77 +1609,14 @@ template class basic_writer { } }; - template struct str_writer { - const Char* s; - size_t size_; - - size_t size() const { return size_; } - size_t width() const { - return count_code_points(basic_string_view(s, size_)); - } - - template void operator()(It&& it) const { - it = copy_str(s, s + size_, it); - } - }; - - struct bytes_writer { - string_view bytes; - - size_t size() const { return bytes.size(); } - size_t width() const { return bytes.size(); } - - template void operator()(It&& it) const { - const char* data = bytes.data(); - it = copy_str(data, data + size(), it); - } - }; - - template struct pointer_writer { - UIntPtr value; - int num_digits; - - size_t size() const { return to_unsigned(num_digits) + 2; } - size_t width() const { return size(); } - - template void operator()(It&& it) const { - *it++ = static_cast('0'); - *it++ = static_cast('x'); - it = format_uint<4, char_type>(it, value, num_digits); - } - }; + using reserve_iterator = remove_reference_t(), 0))>; public: explicit basic_writer(Range out, locale_ref loc = locale_ref()) : out_(out.begin()), locale_(loc) {} - iterator out() const { return out_; } - - // Writes a value in the format - // - // where is written by f(it). - template void write_padded(const format_specs& specs, F&& f) { - // User-perceived width (in code points). - unsigned width = to_unsigned(specs.width); - size_t size = f.size(); // The number of code units. - size_t num_code_points = width != 0 ? f.width() : size; - if (width <= num_code_points) return f(reserve(size)); - size_t padding = width - num_code_points; - size_t fill_size = specs.fill.size(); - auto&& it = reserve(size + padding * fill_size); - if (specs.align == align::right) { - it = fill(it, padding, specs.fill); - f(it); - } else if (specs.align == align::center) { - std::size_t left_padding = padding / 2; - it = fill(it, left_padding, specs.fill); - f(it); - it = fill(it, padding - left_padding, specs.fill); - } else { - f(it); - it = fill(it, padding, specs.fill); - } - } + iterator& out() { return out_; } void write(int value) { write_decimal(value); } void write(long value) { write_decimal(value); } @@ -1676,13 +1631,14 @@ template class basic_writer { void write(uint128_t value) { write_decimal(value); } #endif - template - void write_int(T value, const Spec& spec) { - handle_int_type_spec(spec.type, int_writer(*this, value, spec)); + template void write_int(T value, const format_specs& spec) { + using uint_type = uint32_or_64_or_128_t; + handle_int_type_spec(spec.type, int_writer(*this, value, spec)); } template ::value)> void write(T value, format_specs specs = {}) { + if (const_check(!is_supported_floating_point(value))) return; 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. @@ -1695,7 +1651,14 @@ template class basic_writer { if (!std::isfinite(value)) { auto str = std::isinf(value) ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); - return write_padded(specs, nonfinite_writer{fspecs.sign, str}); + constexpr size_t str_size = 3; + auto sign = fspecs.sign; + auto size = str_size + (sign ? 1 : 0); + out_ = write_padded(out_, specs, size, [=](reserve_iterator it) { + if (sign) *it++ = static_cast(data::signs[sign]); + return copy_str(str, str + str_size, it); + }); + return; } if (specs.align == align::none) { @@ -1714,8 +1677,7 @@ template class basic_writer { if (fspecs.format == float_format::hex) { if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); snprintf_float(promote_float(value), specs.precision, fspecs, buffer); - write_padded(specs, str_writer{buffer.data(), buffer.size()}); - return; + return write_bytes({buffer.data(), buffer.size()}, specs); } int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; if (fspecs.format == float_format::exp) { @@ -1726,18 +1688,13 @@ template class basic_writer { } if (const_check(std::is_same())) fspecs.binary32 = true; fspecs.use_grisu = use_grisu(); - if (const_check(FMT_DEPRECATED_PERCENT) && fspecs.percent) value *= 100; int exp = format_float(promote_float(value), precision, fspecs, buffer); - if (const_check(FMT_DEPRECATED_PERCENT) && fspecs.percent) { - buffer.push_back('%'); - --exp; // Adjust decimal place position. - } fspecs.precision = precision; char_type point = fspecs.locale ? decimal_point(locale_) : static_cast('.'); - write_padded(specs, float_writer(buffer.data(), - static_cast(buffer.size()), - exp, fspecs, point)); + float_writer w(buffer.data(), static_cast(buffer.size()), + exp, fspecs, point); + out_ = write_padded(out_, specs, w.size(), w); } void write(char value) { @@ -1763,7 +1720,12 @@ template class basic_writer { template void write(const Char* s, std::size_t size, const format_specs& specs) { - write_padded(specs, str_writer{s, size}); + auto width = specs.width != 0 + ? count_code_points(basic_string_view(s, size)) + : 0; + out_ = write_padded(out_, specs, size, width, [=](reserve_iterator it) { + return copy_str(s, s + size, it); + }); } template @@ -1776,17 +1738,26 @@ template class basic_writer { } void write_bytes(string_view bytes, const format_specs& specs) { - write_padded(specs, bytes_writer{bytes}); + out_ = + write_padded(out_, specs, bytes.size(), [bytes](reserve_iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); } template void write_pointer(UIntPtr value, const format_specs* specs) { int num_digits = count_digits<4>(value); - auto pw = pointer_writer{value, num_digits}; - if (!specs) return pw(reserve(to_unsigned(num_digits) + 2)); + auto size = to_unsigned(num_digits) + size_t(2); + auto write = [=](reserve_iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_uint<4, char_type>(it, value, num_digits); + }; + if (!specs) return void(write(reserve(size))); format_specs specs_copy = *specs; if (specs_copy.align == align::none) specs_copy.align = align::right; - write_padded(specs_copy, pw); + out_ = write_padded(out_, specs_copy, size, write); } }; @@ -1812,14 +1783,17 @@ class arg_formatter_base { char_type value; size_t size() const { return 1; } - size_t width() const { return 1; } - template void operator()(It&& it) const { *it++ = value; } + template It operator()(It it) const { + *it++ = value; + return it; + } }; void write_char(char_type value) { if (specs_) - writer_.write_padded(*specs_, char_writer{value}); + writer_.out() = + write_padded(writer_.out(), *specs_, 1, char_writer{value}); else writer_.write(value); } @@ -1830,7 +1804,6 @@ class arg_formatter_base { protected: writer_type& writer() { return writer_; } - FMT_DEPRECATED format_specs* spec() { return specs_; } format_specs* specs() { return specs_; } iterator out() { return writer_.out(); } @@ -1881,7 +1854,10 @@ class arg_formatter_base { template ::value)> iterator operator()(T value) { - writer_.write(value, specs_ ? *specs_ : format_specs()); + if (const_check(is_supported_floating_point(value))) + writer_.write(value, specs_ ? *specs_ : format_specs()); + else + FMT_ASSERT(false, "unsupported float argument type"); return out(); } @@ -1894,7 +1870,7 @@ class arg_formatter_base { void on_int() { if (formatter.specs_) - formatter.writer_.write_int(value, *formatter.specs_); + formatter.writer_.write_int(static_cast(value), *formatter.specs_); else formatter.writer_.write(value); } @@ -1947,10 +1923,6 @@ template FMT_CONSTEXPR int parse_nonnegative_int(const Char*& begin, const Char* end, ErrorHandler&& eh) { FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); - if (*begin == '0') { - ++begin; - return 0; - } unsigned value = 0; // Convert to unsigned to prevent a warning. constexpr unsigned max_int = max_value(); @@ -2102,12 +2074,18 @@ template class numeric_specs_checker { // A format specifier handler that checks if specifiers are consistent with the // argument type. template class specs_checker : public Handler { + private: + numeric_specs_checker checker_; + + // Suppress an MSVC warning about using this in initializer list. + FMT_CONSTEXPR Handler& error_handler() { return *this; } + public: FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) - : Handler(handler), checker_(*this, arg_type) {} + : Handler(handler), checker_(error_handler(), arg_type) {} FMT_CONSTEXPR specs_checker(const specs_checker& other) - : Handler(other), checker_(*this, other.arg_type_) {} + : Handler(other), checker_(error_handler(), other.arg_type_) {} FMT_CONSTEXPR void on_align(align_t align) { if (align == align::numeric) checker_.require_numeric_argument(); @@ -2140,9 +2118,6 @@ template class specs_checker : public Handler { } FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } - - private: - numeric_specs_checker checker_; }; template