diff --git a/.gitignore b/.gitignore index 694f8f8f..e95cbaa8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .vscode/ +.vs/ *.iml .idea/ @@ -14,6 +15,7 @@ bin/ /CMakeScripts /doc/doxyxml /doc/html +/doc/node_modules virtualenv /Testing /install_manifest.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b90e3624..0d63ac9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,11 @@ if (MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE) "CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.") endif () +include(GNUInstallDirs) +set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING + "Installation directory for include files, a relative path " + "that will be joined to ${CMAKE_INSTALL_PREFIX}, or an arbitrary absolute path.") + option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF) option(FMT_WERROR "Halt the compilation with an error on compiler warnings." OFF) @@ -54,7 +59,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 @@ -79,6 +84,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) @@ -174,7 +180,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) @@ -194,7 +204,7 @@ target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES}) target_include_directories(fmt PUBLIC $ - $) + $) set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.") @@ -203,8 +213,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 () @@ -229,42 +240,36 @@ target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES}) target_include_directories(fmt-header-only INTERFACE $ - $) + $) # Install targets. 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) set(targets_export_name fmt-targets) - set (INSTALL_TARGETS fmt) - if (TARGET fmt-header-only) - set(INSTALL_TARGETS ${INSTALL_TARGETS} fmt-header-only) - endif () - set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING - "Installation directory for libraries, relative to " - "${CMAKE_INSTALL_PREFIX}.") - - set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR}/fmt CACHE STRING - "Installation directory for include files, 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_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}" "${FMT_LIB_DIR}") + join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}") + configure_file( "${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in" "${pkgconfig}" @@ -273,6 +278,8 @@ if (FMT_INSTALL) ${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in ${project_config} INSTALL_DESTINATION ${FMT_CMAKE_DIR}) + + set(INSTALL_TARGETS fmt fmt-header-only) # Use a namespace because CMake provides better diagnostics for namespaced # imported targets. export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt:: @@ -293,7 +300,7 @@ if (FMT_INSTALL) install(FILES $ DESTINATION ${FMT_LIB_DIR} OPTIONAL) - install(FILES ${FMT_HEADERS} DESTINATION ${FMT_INC_DIR}) + install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt") install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}") endif () @@ -309,6 +316,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..81ee76b1 100644 --- a/ChangeLog.rst +++ b/ChangeLog.rst @@ -1,3 +1,226 @@ +6.2.1 - 2020-05-09 +------------------ + +* Fixed ostream support in ``sprintf`` + (`#1631 `_). + +* Fixed type detection when using implicit conversion to ``string_view`` and + ostream ``operator<<`` inconsistently + (`#1662 `_). + +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..48da9ed3 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 @@ -291,27 +301,24 @@ Projects using this library * `Kodi `_ (formerly xbmc): Home theater software -* `Lifeline `_: A 2D game +* `Knuth `_: High-performance Bitcoin full-node -* `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 +* `Microsoft Verona `_: Research programming language for concurrent ownership * `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 +327,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 +352,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/CMakeLists.txt b/doc/CMakeLists.txt index f3dae606..108aa71e 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -9,4 +9,5 @@ add_custom_target(doc SOURCES api.rst syntax.rst usage.rst build.py conf.py _templates/layout.html) install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html/ - DESTINATION share/doc/fmt OPTIONAL) + DESTINATION share/doc/fmt OPTIONAL + PATTERN ".doctrees" EXCLUDE) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index d5f4b70d..333a606c 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -6,14 +6,13 @@ {# Google Analytics #} + {% endblock %} diff --git a/doc/api.rst b/doc/api.rst index 4e370252..8aefbdb7 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: @@ -68,6 +69,9 @@ Argument Lists .. doxygenclass:: fmt::format_arg_store :members: +.. doxygenclass:: fmt::dynamic_format_arg_store + :members: + .. doxygenclass:: fmt::basic_format_args :members: @@ -96,7 +100,7 @@ locale:: #include std::locale::global(std::locale("en_US.UTF-8")); - auto s = fmt::format("{:n}", 1000000); // s == "1,000,000" + auto s = fmt::format("{:L}", 1000000); // s == "1,000,000" .. _format-api: @@ -104,7 +108,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 +182,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 +196,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 @@ -238,9 +250,9 @@ Literal-based API The following user-defined literals are defined in ``fmt/format.h``. -.. doxygenfunction:: operator""_format(const char *, std::size_t) +.. doxygenfunction:: operator""_format(const char *, size_t) -.. doxygenfunction:: operator""_a(const char *, std::size_t) +.. doxygenfunction:: operator""_a(const char *, size_t) Utilities --------- @@ -261,6 +273,9 @@ Utilities .. doxygenfunction:: fmt::join(It, It, string_view) +.. doxygenclass:: fmt::detail::buffer + :members: + .. doxygenclass:: fmt::basic_memory_buffer :protected-members: :members: @@ -399,7 +414,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..e5bcbf3b 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', '6.2.1'] def pip_install(package, commit=None, **kwargs): "Install package using pip." diff --git a/doc/syntax.rst b/doc/syntax.rst index af65616a..8265f702 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -79,10 +79,10 @@ The general form of a *standard format specifier* is: fill: align: "<" | ">" | "^" 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" + width: `integer` | "{" [`arg_id`] "}" + precision: `integer` | "{" [`arg_id`] "}" + 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..938f117a 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -48,7 +48,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // From fits in To without any problem. } else { // From does not always fit in To, resort to a dynamic check. - if (from < T::min() || from > T::max()) { + if (from < (T::min)() || from > (T::max)()) { // outside range. ec = 1; return {}; @@ -74,7 +74,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { if (F::is_signed && !T::is_signed) { // From may be negative, not allowed! - if (fmt::internal::is_negative(from)) { + if (fmt::detail::is_negative(from)) { ec = 1; return {}; } @@ -84,7 +84,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // yes, From always fits in To. } else { // from may not fit in To, we have to do a dynamic check - if (from > static_cast(T::max())) { + if (from > static_cast((T::max)())) { ec = 1; return {}; } @@ -97,7 +97,7 @@ FMT_CONSTEXPR To lossless_integral_conversion(const From from, int& ec) { // yes, From always fits in To. } else { // from may not fit in To, we have to do a dynamic check - if (from > static_cast(T::max())) { + if (from > static_cast((T::max)())) { // outside range. ec = 1; return {}; @@ -141,7 +141,7 @@ FMT_CONSTEXPR To safe_float_conversion(const From from, int& ec) { // catch the only happy case if (std::isfinite(from)) { - if (from >= T::lowest() && from <= T::max()) { + if (from >= T::lowest() && from <= (T::max)()) { return static_cast(from); } // not within range. @@ -195,12 +195,13 @@ To safe_duration_cast(std::chrono::duration from, } // multiply with Factor::num without overflow or underflow if (Factor::num != 1) { - const auto max1 = internal::max_value() / Factor::num; + const auto max1 = detail::max_value() / Factor::num; if (count > max1) { ec = 1; return {}; } - const auto min1 = std::numeric_limits::min() / Factor::num; + const auto min1 = + (std::numeric_limits::min)() / Factor::num; if (count < min1) { ec = 1; return {}; @@ -269,7 +270,7 @@ To safe_duration_cast(std::chrono::duration from, // multiply with Factor::num without overflow or underflow if (Factor::num != 1) { - constexpr auto max1 = internal::max_value() / + constexpr auto max1 = detail::max_value() / static_cast(Factor::num); if (count > max1) { ec = 1; @@ -306,12 +307,12 @@ To safe_duration_cast(std::chrono::duration from, // Usage: f FMT_NOMACRO() #define FMT_NOMACRO -namespace internal { +namespace detail { inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } inline null<> localtime_s(...) { return null<>(); } inline null<> gmtime_r(...) { return null<>(); } inline null<> gmtime_s(...) { return null<>(); } -} // namespace internal +} // namespace detail // Thread-safe replacement for std::localtime inline std::tm localtime(std::time_t time) { @@ -322,22 +323,22 @@ inline std::tm localtime(std::time_t time) { dispatcher(std::time_t t) : time_(t) {} bool run() { - using namespace fmt::internal; + using namespace fmt::detail; return handle(localtime_r(&time_, &tm_)); } bool handle(std::tm* tm) { return tm != nullptr; } - bool handle(internal::null<>) { - using namespace fmt::internal; + bool handle(detail::null<>) { + using namespace fmt::detail; return fallback(localtime_s(&tm_, &time_)); } bool fallback(int res) { return res == 0; } #if !FMT_MSC_VER - bool fallback(internal::null<>) { - using namespace fmt::internal; + bool fallback(detail::null<>) { + using namespace fmt::detail; std::tm* tm = std::localtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -359,21 +360,21 @@ inline std::tm gmtime(std::time_t time) { dispatcher(std::time_t t) : time_(t) {} bool run() { - using namespace fmt::internal; + using namespace fmt::detail; return handle(gmtime_r(&time_, &tm_)); } bool handle(std::tm* tm) { return tm != nullptr; } - bool handle(internal::null<>) { - using namespace fmt::internal; + bool handle(detail::null<>) { + using namespace fmt::detail; return fallback(gmtime_s(&tm_, &time_)); } bool fallback(int res) { return res == 0; } #if !FMT_MSC_VER - bool fallback(internal::null<>) { + bool fallback(detail::null<>) { std::tm* tm = std::gmtime(&time_); if (tm) tm_ = *tm; return tm != nullptr; @@ -386,17 +387,17 @@ inline std::tm gmtime(std::time_t time) { return gt.tm_; } -namespace internal { -inline std::size_t strftime(char* str, std::size_t count, const char* format, - const std::tm* time) { +namespace detail { +inline size_t strftime(char* str, size_t count, const char* format, + const std::tm* time) { return std::strftime(str, count, format, time); } -inline std::size_t strftime(wchar_t* str, std::size_t count, - const wchar_t* format, const std::tm* time) { +inline size_t strftime(wchar_t* str, size_t count, const wchar_t* format, + const std::tm* time) { return std::wcsftime(str, count, format, time); } -} // namespace internal +} // namespace detail template struct formatter { template @@ -405,7 +406,7 @@ template struct formatter { if (it != ctx.end() && *it == ':') ++it; auto end = it; while (end != ctx.end() && *end != '}') ++end; - tm_format.reserve(internal::to_unsigned(end - it + 1)); + tm_format.reserve(detail::to_unsigned(end - it + 1)); tm_format.append(it, end); tm_format.push_back('\0'); return end; @@ -414,11 +415,10 @@ template struct formatter { template auto format(const std::tm& tm, FormatContext& ctx) -> decltype(ctx.out()) { basic_memory_buffer buf; - std::size_t start = buf.size(); + size_t start = buf.size(); for (;;) { - std::size_t size = buf.capacity() - start; - std::size_t count = - internal::strftime(&buf[start], size, &tm_format[0], &tm); + size_t size = buf.capacity() - start; + size_t count = detail::strftime(&buf[start], size, &tm_format[0], &tm); if (count != 0) { buf.resize(start + count); break; @@ -430,7 +430,7 @@ template struct formatter { // https://github.com/fmtlib/fmt/issues/367 break; } - const std::size_t MIN_GROWTH = 10; + const size_t MIN_GROWTH = 10; buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); } return std::copy(buf.begin(), buf.end(), ctx.out()); @@ -439,7 +439,7 @@ template struct formatter { basic_memory_buffer tm_format; }; -namespace internal { +namespace detail { template FMT_CONSTEXPR const char* get_units() { return nullptr; } @@ -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}; @@ -874,7 +880,7 @@ struct chrono_formatter { if (isnan(value)) return write_nan(); uint32_or_64_or_128_t n = to_unsigned(to_nonnegative_int(value, max_value())); - int num_digits = internal::count_digits(n); + int num_digits = detail::count_digits(n); if (width > num_digits) out = std::fill_n(out, width - num_digits, '0'); out = format_decimal(out, n, num_digits); } @@ -1004,14 +1010,14 @@ struct chrono_formatter { out = format_duration_unit(out); } }; -} // namespace internal +} // namespace detail template struct formatter, Char> { private: basic_format_specs specs; int precision; - using arg_ref_type = internal::arg_ref; + using arg_ref_type = detail::arg_ref; arg_ref_type width_ref; arg_ref_type precision_ref; mutable basic_string_view format_str; @@ -1032,7 +1038,7 @@ struct formatter, Char> { return arg_ref_type(arg_id); } - FMT_CONSTEXPR arg_ref_type make_arg_ref(internal::auto_id) { + FMT_CONSTEXPR arg_ref_type make_arg_ref(detail::auto_id) { return arg_ref_type(context.next_arg_id()); } @@ -1062,17 +1068,17 @@ struct formatter, Char> { auto begin = ctx.begin(), end = ctx.end(); if (begin == end || *begin == '}') return {begin, begin}; spec_handler handler{*this, ctx, format_str}; - begin = internal::parse_align(begin, end, handler); + begin = detail::parse_align(begin, end, handler); if (begin == end) return {begin, begin}; - begin = internal::parse_width(begin, end, handler); + begin = detail::parse_width(begin, end, handler); if (begin == end) return {begin, begin}; if (*begin == '.') { if (std::is_floating_point::value) - begin = internal::parse_precision(begin, end, handler); + begin = detail::parse_precision(begin, end, handler); else handler.on_error("precision not allowed for this argument type"); } - end = parse_chrono_format(begin, end, internal::chrono_format_checker()); + end = parse_chrono_format(begin, end, detail::chrono_format_checker()); return {begin, end}; } @@ -1083,7 +1089,7 @@ struct formatter, Char> { -> decltype(ctx.begin()) { auto range = do_parse(ctx); format_str = basic_string_view( - &*range.begin, internal::to_unsigned(range.end - range.begin)); + &*range.begin, detail::to_unsigned(range.end - range.begin)); return range.end; } @@ -1094,23 +1100,21 @@ struct formatter, Char> { // is not specified. basic_memory_buffer buf; auto out = std::back_inserter(buf); - using range = internal::output_range; - internal::basic_writer w(range(ctx.out())); - internal::handle_dynamic_spec(specs.width, - width_ref, ctx); - internal::handle_dynamic_spec( - precision, precision_ref, ctx); + detail::handle_dynamic_spec(specs.width, width_ref, + ctx); + detail::handle_dynamic_spec(precision, + precision_ref, ctx); if (begin == end || *begin == '}') { - out = internal::format_duration_value(out, d.count(), precision); - internal::format_duration_unit(out); + out = detail::format_duration_value(out, d.count(), precision); + detail::format_duration_unit(out); } else { - internal::chrono_formatter f( + detail::chrono_formatter f( ctx, out, d); f.precision = precision; parse_chrono_format(begin, end, f); } - w.write(buf.data(), buf.size(), specs); - return w.out(); + return detail::write( + ctx.out(), basic_string_view(buf.data(), buf.size()), specs); } }; diff --git a/include/fmt/color.h b/include/fmt/color.h index 96d9ab6b..b65f892a 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -198,7 +198,7 @@ struct rgb { uint8_t b; }; -namespace internal { +namespace detail { // color is a struct of either a rgb color or a terminal color. struct color_type { @@ -221,7 +221,7 @@ struct color_type { uint32_t rgb_color; } value; }; -} // namespace internal +} // namespace detail // Experimental text formatting support. class text_style { @@ -298,11 +298,11 @@ class text_style { FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT { return static_cast(ems) != 0; } - FMT_CONSTEXPR internal::color_type get_foreground() const FMT_NOEXCEPT { + FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT { FMT_ASSERT(has_foreground(), "no foreground specified for this style"); return foreground_color; } - FMT_CONSTEXPR internal::color_type get_background() const FMT_NOEXCEPT { + FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT { FMT_ASSERT(has_background(), "no background specified for this style"); return background_color; } @@ -313,7 +313,7 @@ class text_style { private: FMT_CONSTEXPR text_style(bool is_foreground, - internal::color_type text_color) FMT_NOEXCEPT + detail::color_type text_color) FMT_NOEXCEPT : set_foreground_color(), set_background_color(), ems() { @@ -326,23 +326,23 @@ class text_style { } } - friend FMT_CONSTEXPR_DECL text_style fg(internal::color_type foreground) + friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground) FMT_NOEXCEPT; - friend FMT_CONSTEXPR_DECL text_style bg(internal::color_type background) + friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background) FMT_NOEXCEPT; - internal::color_type foreground_color; - internal::color_type background_color; + detail::color_type foreground_color; + detail::color_type background_color; bool set_foreground_color; bool set_background_color; emphasis ems; }; -FMT_CONSTEXPR text_style fg(internal::color_type foreground) FMT_NOEXCEPT { +FMT_CONSTEXPR text_style fg(detail::color_type foreground) FMT_NOEXCEPT { return text_style(/*is_foreground=*/true, foreground); } -FMT_CONSTEXPR text_style bg(internal::color_type background) FMT_NOEXCEPT { +FMT_CONSTEXPR text_style bg(detail::color_type background) FMT_NOEXCEPT { return text_style(/*is_foreground=*/false, background); } @@ -350,21 +350,21 @@ FMT_CONSTEXPR text_style operator|(emphasis lhs, emphasis rhs) FMT_NOEXCEPT { return text_style(lhs) | rhs; } -namespace internal { +namespace detail { template struct ansi_color_escape { - FMT_CONSTEXPR ansi_color_escape(internal::color_type text_color, + FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color, const char* esc) FMT_NOEXCEPT { // If we have a terminal color, we need to output another escape code // sequence. if (!text_color.is_rgb) { - bool is_background = esc == internal::data::background_color; + bool is_background = esc == detail::data::background_color; uint32_t value = text_color.value.term_color; // Background ASCII codes are the same as the foreground ones but with // 10 more. if (is_background) value += 10u; - std::size_t index = 0; + size_t index = 0; buffer[index++] = static_cast('\x1b'); buffer[index++] = static_cast('['); @@ -398,7 +398,7 @@ template struct ansi_color_escape { if (em_bits & static_cast(emphasis::strikethrough)) em_codes[3] = 9; - std::size_t index = 0; + size_t index = 0; for (int i = 0; i < 4; ++i) { if (!em_codes[i]) continue; buffer[index++] = static_cast('\x1b'); @@ -429,14 +429,14 @@ template struct ansi_color_escape { template FMT_CONSTEXPR ansi_color_escape make_foreground_color( - internal::color_type foreground) FMT_NOEXCEPT { - return ansi_color_escape(foreground, internal::data::foreground_color); + detail::color_type foreground) FMT_NOEXCEPT { + return ansi_color_escape(foreground, detail::data::foreground_color); } template FMT_CONSTEXPR ansi_color_escape make_background_color( - internal::color_type background) FMT_NOEXCEPT { - return ansi_color_escape(background, internal::data::background_color); + detail::color_type background) FMT_NOEXCEPT { + return ansi_color_escape(background, detail::data::background_color); } template @@ -455,11 +455,11 @@ inline void fputs(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT { } template inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(internal::data::reset_color, stream); + fputs(detail::data::reset_color, stream); } template <> inline void reset_color(FILE* stream) FMT_NOEXCEPT { - fputs(internal::data::wreset_color, stream); + fputs(detail::data::wreset_color, stream); } template @@ -476,33 +476,31 @@ void vformat_to(basic_memory_buffer& buf, const text_style& ts, bool has_style = false; if (ts.has_emphasis()) { has_style = true; - auto emphasis = internal::make_emphasis(ts.get_emphasis()); + auto emphasis = detail::make_emphasis(ts.get_emphasis()); buf.append(emphasis.begin(), emphasis.end()); } if (ts.has_foreground()) { has_style = true; - auto foreground = - internal::make_foreground_color(ts.get_foreground()); + auto foreground = detail::make_foreground_color(ts.get_foreground()); buf.append(foreground.begin(), foreground.end()); } if (ts.has_background()) { has_style = true; - auto background = - internal::make_background_color(ts.get_background()); + auto background = detail::make_background_color(ts.get_background()); buf.append(background.begin(), background.end()); } - internal::vformat_to(buf, format_str, args); - if (has_style) internal::reset_color(buf); + detail::vformat_to(buf, format_str, args); + if (has_style) detail::reset_color(buf); } -} // namespace internal +} // namespace detail template > void vprint(std::FILE* f, const text_style& ts, const S& format, basic_format_args> args) { basic_memory_buffer buf; - internal::vformat_to(buf, ts, to_string_view(format), args); + detail::vformat_to(buf, ts, to_string_view(format), args); buf.push_back(Char(0)); - internal::fputs(buf.data(), f); + detail::fputs(buf.data(), f); } /** @@ -513,10 +511,10 @@ void vprint(std::FILE* f, const text_style& ts, const S& format, "Elapsed time: {0:.2f} seconds", 1.23); */ template ::value)> + FMT_ENABLE_IF(detail::is_string::value)> void print(std::FILE* f, const text_style& ts, const S& format_str, const Args&... args) { - internal::check_format_string(format_str); + detail::check_format_string(format_str); using context = buffer_context>; format_arg_store as{args...}; vprint(f, ts, format_str, basic_format_args(as)); @@ -530,7 +528,7 @@ void print(std::FILE* f, const text_style& ts, const S& format_str, "Elapsed time: {0:.2f} seconds", 1.23); */ template ::value)> + FMT_ENABLE_IF(detail::is_string::value)> void print(const text_style& ts, const S& format_str, const Args&... args) { return print(stdout, ts, format_str, args...); } @@ -540,7 +538,7 @@ inline std::basic_string vformat( const text_style& ts, const S& format_str, basic_format_args>> args) { basic_memory_buffer buf; - internal::vformat_to(buf, ts, to_string_view(format_str), args); + detail::vformat_to(buf, ts, to_string_view(format_str), args); return fmt::to_string(buf); } @@ -560,7 +558,7 @@ template > inline std::basic_string format(const text_style& ts, const S& format_str, const Args&... args) { return vformat(ts, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); + detail::make_args_checked(format_str, args...)); } FMT_END_NAMESPACE diff --git a/include/fmt/compile.h b/include/fmt/compile.h index e4b12f34..fb66ae7c 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -13,7 +13,7 @@ #include "format.h" FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { // Part of a compiled format string. It can be either literal text or a // replacement field. @@ -62,13 +62,15 @@ template struct part_counter { if (begin != end) ++num_parts; } - FMT_CONSTEXPR void on_arg_id() { ++num_parts; } - FMT_CONSTEXPR void on_arg_id(int) { ++num_parts; } - FMT_CONSTEXPR void on_arg_id(basic_string_view) { ++num_parts; } + FMT_CONSTEXPR int on_arg_id() { return ++num_parts, 0; } + FMT_CONSTEXPR int on_arg_id(int) { return ++num_parts, 0; } + FMT_CONSTEXPR int on_arg_id(basic_string_view) { + return ++num_parts, 0; + } - FMT_CONSTEXPR void on_replacement_field(const Char*) {} + FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} - FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, + FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, const Char* end) { // Find the matching brace. unsigned brace_counter = 0; @@ -116,25 +118,28 @@ class format_string_compiler : public error_handler { handler_(part::make_text({begin, to_unsigned(end - begin)})); } - FMT_CONSTEXPR void on_arg_id() { + FMT_CONSTEXPR int on_arg_id() { part_ = part::make_arg_index(parse_context_.next_arg_id()); + return 0; } - FMT_CONSTEXPR void on_arg_id(int id) { + FMT_CONSTEXPR int on_arg_id(int id) { parse_context_.check_arg_id(id); part_ = part::make_arg_index(id); + return 0; } - FMT_CONSTEXPR void on_arg_id(basic_string_view id) { + FMT_CONSTEXPR int on_arg_id(basic_string_view id) { part_ = part::make_arg_name(id); + return 0; } - FMT_CONSTEXPR void on_replacement_field(const Char* ptr) { + FMT_CONSTEXPR void on_replacement_field(int, const Char* ptr) { part_.arg_id_end = ptr; handler_(part_); } - FMT_CONSTEXPR const Char* on_format_specs(const Char* begin, + FMT_CONSTEXPR const Char* on_format_specs(int, const Char* begin, const Char* end) { auto repl = typename part::replacement(); dynamic_specs_handler> handler( @@ -160,23 +165,24 @@ FMT_CONSTEXPR void compile_format_string(basic_string_view format_str, format_string_compiler(format_str, handler)); } -template +template void format_arg( - basic_format_parse_context& parse_ctx, + basic_format_parse_context& parse_ctx, Context& ctx, Id arg_id) { - ctx.advance_to( - visit_format_arg(arg_formatter(ctx, &parse_ctx), ctx.arg(arg_id))); + ctx.advance_to(visit_format_arg( + arg_formatter(ctx, &parse_ctx), + ctx.arg(arg_id))); } // vformat_to is defined in a subnamespace to prevent ADL. namespace cf { -template -auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) - -> typename Context::iterator { +template +auto vformat_to(OutputIt out, CompiledFormat& cf, + basic_format_args args) -> typename Context::iterator { using char_type = typename Context::char_type; basic_format_parse_context parse_ctx( to_string_view(cf.format_str_)); - Context ctx(out.begin(), args); + Context ctx(out, args); const auto& parts = cf.parts(); for (auto part_it = std::begin(parts); part_it != std::end(parts); @@ -197,12 +203,12 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) case format_part_t::kind::arg_index: advance_to(parse_ctx, part.arg_id_end); - internal::format_arg(parse_ctx, ctx, value.arg_index); + detail::format_arg(parse_ctx, ctx, value.arg_index); break; case format_part_t::kind::arg_name: advance_to(parse_ctx, part.arg_id_end); - internal::format_arg(parse_ctx, ctx, value.str); + detail::format_arg(parse_ctx, ctx, value.str); break; case format_part_t::kind::replacement: { @@ -226,7 +232,9 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) advance_to(parse_ctx, part.arg_id_end); ctx.advance_to( - visit_format_arg(arg_formatter(ctx, nullptr, &specs), arg)); + visit_format_arg(arg_formatter( + ctx, nullptr, &specs), + arg)); break; } } @@ -240,7 +248,7 @@ struct basic_compiled_format {}; template struct compiled_format_base : basic_compiled_format { using char_type = char_t; - using parts_container = std::vector>; + using parts_container = std::vector>; parts_container compiled_parts; @@ -305,7 +313,7 @@ struct compiled_format_base::value>> const parts_container& parts() const { static FMT_CONSTEXPR_DECL const auto compiled_parts = compile_to_parts( - internal::to_string_view(S())); + detail::to_string_view(S())); return compiled_parts.data; } }; @@ -318,8 +326,8 @@ class compiled_format : private compiled_format_base { private: basic_string_view format_str_; - template - friend auto cf::vformat_to(Range out, CompiledFormat& cf, + template + friend auto cf::vformat_to(OutputIt out, CompiledFormat& cf, basic_format_args args) -> typename Context::iterator; @@ -383,9 +391,7 @@ OutputIt format_default(OutputIt out, T value) { template OutputIt format_default(OutputIt out, double value) { - writer w(out); - w.write(value); - return w.out(); + return detail::write(out, value); } template @@ -495,7 +501,7 @@ constexpr auto compile_format_string(S format_str) { } } #endif // __cpp_if_constexpr -} // namespace internal +} // namespace detail #if FMT_USE_CONSTEXPR # ifdef __cpp_if_constexpr @@ -504,14 +510,14 @@ template str = format_str; if constexpr (str.size() == 0) { - return internal::make_text(str, 0, 0); + return detail::make_text(str, 0, 0); } else { constexpr auto result = - internal::compile_format_string, 0, 0>( + detail::compile_format_string, 0, 0>( format_str); if constexpr (std::is_same, - internal::unknown_format>()) { - return internal::compiled_format(to_string_view(format_str)); + detail::unknown_format>()) { + return detail::compiled_format(to_string_view(format_str)); } else { return result; } @@ -520,7 +526,7 @@ constexpr auto compile(S format_str) { template ::value)> + FMT_ENABLE_IF(detail::is_compiled_format::value)> std::basic_string format(const CompiledFormat& cf, const Args&... args) { basic_memory_buffer buffer; cf.format(std::back_inserter(buffer), args...); @@ -528,7 +534,7 @@ std::basic_string format(const CompiledFormat& cf, const Args&... args) { } template ::value)> + FMT_ENABLE_IF(detail::is_compiled_format::value)> OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { return cf.format(out, args...); @@ -536,8 +542,8 @@ OutputIt format_to(OutputIt out, const CompiledFormat& cf, # else template ::value)> -constexpr auto compile(S format_str) -> internal::compiled_format { - return internal::compiled_format(to_string_view(format_str)); +constexpr auto compile(S format_str) -> detail::compiled_format { + return detail::compiled_format(to_string_view(format_str)); } # endif // __cpp_if_constexpr #endif // FMT_USE_CONSTEXPR @@ -545,49 +551,50 @@ constexpr auto compile(S format_str) -> internal::compiled_format { // Compiles the format string which must be a string literal. template auto compile(const Char (&format_str)[N]) - -> internal::compiled_format { - return internal::compiled_format( + -> detail::compiled_format { + return detail::compiled_format( basic_string_view(format_str, N - 1)); } template ::value)> std::basic_string format(const CompiledFormat& cf, const Args&... args) { basic_memory_buffer buffer; - using range = buffer_range; using context = buffer_context; - internal::cf::vformat_to(range(buffer), cf, - make_format_args(args...)); + detail::buffer& base = buffer; + detail::cf::vformat_to(std::back_inserter(base), cf, + make_format_args(args...)); return to_string(buffer); } template ::value)> OutputIt format_to(OutputIt out, const CompiledFormat& cf, const Args&... args) { using char_type = typename CompiledFormat::char_type; - using range = internal::output_range; using context = format_context_t; - return internal::cf::vformat_to(range(out), cf, - make_format_args(args...)); + return detail::cf::vformat_to(out, cf, + make_format_args(args...)); } -template ::value)> +template < + typename OutputIt, typename CompiledFormat, typename... Args, + FMT_ENABLE_IF(detail::is_output_iterator::value&& std::is_base_of< + detail::basic_compiled_format, CompiledFormat>::value)> format_to_n_result format_to_n(OutputIt out, size_t n, const CompiledFormat& cf, const Args&... args) { auto it = - format_to(internal::truncating_iterator(out, n), cf, args...); + format_to(detail::truncating_iterator(out, n), cf, args...); return {it.base(), it.count()}; } template -std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { - return format_to(internal::counting_iterator(), cf, args...).count(); +size_t formatted_size(const CompiledFormat& cf, const Args&... args) { + return format_to(detail::counting_iterator(), cf, args...).count(); } FMT_END_NAMESPACE diff --git a/include/fmt/core.h b/include/fmt/core.h index 70647c76..9985c036 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 60202 +#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 @@ -141,14 +148,6 @@ # define FMT_NORETURN #endif -#ifndef FMT_MAYBE_UNUSED -# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) -# define FMT_MAYBE_UNUSED [[maybe_unused]] -# else -# define FMT_MAYBE_UNUSED -# endif -#endif - #ifndef FMT_DEPRECATED # if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 # define FMT_DEPRECATED [[deprecated]] @@ -164,12 +163,20 @@ #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 #endif +#ifndef FMT_INLINE +# if FMT_GCC_VERSION || FMT_CLANG_VERSION +# define FMT_INLINE inline __attribute__((always_inline)) +# else +# define FMT_INLINE inline +# endif +#endif + #ifndef FMT_BEGIN_NAMESPACE # if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ FMT_MSC_VER >= 1900 @@ -190,20 +197,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 +242,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 @@ -270,26 +272,24 @@ struct monostate {}; // to workaround a bug in MSVC 2019 (see #1140 and #1186). #define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 -namespace internal { +namespace detail { // A helper function to suppress bogus "conditional expression is constant" // warnings. -template FMT_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; }; +template constexpr T const_check(T value) { return value; } FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); #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 */ \ ? (void)0 \ - : ::fmt::internal::assert_fail(__FILE__, __LINE__, (message))) + : ::fmt::detail::assert_fail(__FILE__, __LINE__, (message))) # endif #endif @@ -323,15 +323,21 @@ FMT_CONSTEXPR typename std::make_unsigned::type to_unsigned(Int 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 +} // namespace detail -template -using void_t = typename internal::void_t_impl::type; +namespace internal = detail; // DEPRECATED /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -346,14 +352,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) {} @@ -363,6 +368,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)) {} @@ -373,22 +381,21 @@ template class basic_string_view { : data_(s.data()), size_(s.size()) {} - template < - typename S, - FMT_ENABLE_IF(std::is_same>::value)> + template >::value)> FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), 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; @@ -427,16 +434,11 @@ template class basic_string_view { using string_view = basic_string_view; using wstring_view = basic_string_view; -#ifndef __cpp_char8_t -// 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 {}; @@ -473,9 +475,8 @@ inline basic_string_view to_string_view(basic_string_view s) { } template >::value)> -inline basic_string_view to_string_view( - internal::std_string_view s) { + FMT_ENABLE_IF(!std::is_empty>::value)> +inline basic_string_view to_string_view(detail::std_string_view s) { return s; } @@ -491,7 +492,7 @@ constexpr basic_string_view to_string_view(const S& s) { return s; } -namespace internal { +namespace detail { void to_string_view(...); using fmt::v6::to_string_view; @@ -509,16 +510,16 @@ 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); }; -} // namespace internal +} // namespace detail /** String's character type. */ -template using char_t = typename internal::char_t_impl::type; +template using char_t = typename detail::char_t_impl::type; /** \rst @@ -536,7 +537,7 @@ template using char_t = typename internal::char_t_impl::type; +-----------------------+-------------------------------------+ \endrst */ -template +template class basic_format_parse_context : private ErrorHandler { private: basic_string_view format_str_; @@ -546,26 +547,24 @@ 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) { - format_str_.remove_prefix(internal::to_unsigned(it - begin())); + format_str_.remove_prefix(detail::to_unsigned(it - begin())); } /** @@ -573,6 +572,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; @@ -595,20 +596,15 @@ 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; using wformat_parse_context = basic_format_parse_context; -template -using basic_parse_context FMT_DEPRECATED_ALIAS = - basic_format_parse_context; -using parse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context; -using wparse_context FMT_DEPRECATED_ALIAS = basic_format_parse_context; - template class basic_format_arg; template class basic_format_args; +template class dynamic_format_arg_store; // A formatter for objects of type T. template @@ -617,43 +613,44 @@ 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 using has_formatter = std::is_constructible>; -namespace internal { +namespace detail { -/** A contiguous memory buffer with an optional growing ability. */ +/** + \rst + A contiguous memory buffer with an optional growing ability. It is an internal + class and shouldn't be used directly, only via `~fmt::basic_memory_buffer`. + \endrst + */ template class buffer { private: T* ptr_; - std::size_t size_; - std::size_t capacity_; + size_t size_; + size_t capacity_; protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. - buffer(std::size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} + FMT_SUPPRESS_MSC_WARNING(26495) + buffer(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 + buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT : ptr_(p), size_(sz), capacity_(cap) {} /** Sets the buffer data and capacity. */ - void set(T* buf_data, std::size_t buf_capacity) FMT_NOEXCEPT { + void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { ptr_ = buf_data; capacity_ = buf_capacity; } /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(std::size_t capacity) = 0; + virtual void grow(size_t capacity) = 0; public: using value_type = T; @@ -670,10 +667,10 @@ template class buffer { const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } /** Returns the size of this buffer. */ - std::size_t size() const FMT_NOEXCEPT { return size_; } + size_t size() const FMT_NOEXCEPT { return size_; } /** Returns the capacity of this buffer. */ - std::size_t capacity() const FMT_NOEXCEPT { return capacity_; } + size_t capacity() const FMT_NOEXCEPT { return capacity_; } /** Returns a pointer to the buffer data. */ T* data() FMT_NOEXCEPT { return ptr_; } @@ -684,7 +681,7 @@ template class buffer { /** Resizes the buffer. If T is a POD type new elements may not be initialized. */ - void resize(std::size_t new_size) { + void resize(size_t new_size) { reserve(new_size); size_ = new_size; } @@ -693,7 +690,7 @@ template class buffer { void clear() { size_ = 0; } /** Reserves space to store at least *capacity* elements. */ - void reserve(std::size_t new_capacity) { + void reserve(size_t new_capacity) { if (new_capacity > capacity_) grow(new_capacity); } @@ -705,8 +702,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. @@ -716,7 +715,7 @@ class container_buffer : public buffer { Container& container_; protected: - void grow(std::size_t capacity) FMT_OVERRIDE { + void grow(size_t capacity) FMT_OVERRIDE { container_.resize(capacity); this->set(&container_[0], capacity); } @@ -747,12 +746,78 @@ template using has_fallback_formatter = std::is_constructible>; -template struct named_arg_base; -template struct named_arg; +struct view {}; + +template struct named_arg : view { + const Char* name; + const T& value; + named_arg(const Char* n, const T& v) : name(n), value(v) {} +}; + +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, // Integer types should go first, int_type, uint_type, @@ -783,7 +848,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); @@ -799,23 +863,26 @@ 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; } template struct string_value { const Char* data; - std::size_t size; + size_t size; +}; + +template struct named_arg_value { + const named_arg_info* data; + size_t size; }; template struct custom_value { - using parse_context = basic_format_parse_context; + using parse_context = typename Context::parse_context_type; const void* value; void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); }; @@ -840,28 +907,30 @@ template class value { const void* pointer; string_value string; custom_value custom; - const named_arg_base* named_arg; + 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 @@ -872,14 +941,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)); @@ -959,6 +1026,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; } @@ -990,11 +1065,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(...) { @@ -1014,23 +1087,22 @@ using mapped_type_constant = type_constant().map(std::declval())), typename Context::char_type>; -enum { packed_arg_bits = 5 }; +enum { packed_arg_bits = 4 }; // 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 }; - -template class arg_map; -} // namespace internal +enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; +} // namespace detail // A formatting argument. It is a trivially copyable/constructible type to // allow storage in basic_memory_buffer. template class basic_format_arg { private: - internal::value value_; - internal::type type_; + detail::value value_; + detail::type type_; template - friend FMT_CONSTEXPR basic_format_arg internal::make_arg( + friend FMT_CONSTEXPR basic_format_arg detail::make_arg( const T& value); template @@ -1039,34 +1111,40 @@ template class basic_format_arg { -> decltype(vis(0)); friend class basic_format_args; - friend class internal::arg_map; + friend class dynamic_format_arg_store; using char_type = typename Context::char_type; + template + friend struct detail::arg_data; + + basic_format_arg(const detail::named_arg_info* args, size_t size) + : value_(args, size) {} + public: class handle { public: - explicit handle(internal::custom_value custom) : custom_(custom) {} + explicit handle(detail::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); } private: - internal::custom_value custom_; + detail::custom_value custom_; }; - FMT_CONSTEXPR basic_format_arg() : type_(internal::type::none_type) {} + constexpr basic_format_arg() : type_(detail::type::none_type) {} - FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { - return type_ != internal::type::none_type; + constexpr explicit operator bool() const FMT_NOEXCEPT { + return type_ != detail::type::none_type; } - internal::type type() const { return type_; } + detail::type type() const { return type_; } - bool is_integral() const { return internal::is_integral_type(type_); } - bool is_arithmetic() const { return internal::is_arithmetic_type(type_); } + bool is_integral() const { return detail::is_integral_type(type_); } + bool is_arithmetic() const { return detail::is_arithmetic_type(type_); } }; /** @@ -1077,92 +1155,73 @@ template class basic_format_arg { \endrst */ template -FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, - const basic_format_arg& arg) - -> decltype(vis(0)) { +FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( + Visitor&& vis, const basic_format_arg& arg) -> decltype(vis(0)) { using char_type = typename Context::char_type; switch (arg.type_) { - case internal::type::none_type: + case detail::type::none_type: break; - case internal::type::named_arg_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case internal::type::int_type: + case detail::type::int_type: return vis(arg.value_.int_value); - case internal::type::uint_type: + case detail::type::uint_type: return vis(arg.value_.uint_value); - case internal::type::long_long_type: + case detail::type::long_long_type: return vis(arg.value_.long_long_value); - case internal::type::ulong_long_type: + case detail::type::ulong_long_type: return vis(arg.value_.ulong_long_value); #if FMT_USE_INT128 - case internal::type::int128_type: + case detail::type::int128_type: return vis(arg.value_.int128_value); - case internal::type::uint128_type: + case detail::type::uint128_type: return vis(arg.value_.uint128_value); #else - case internal::type::int128_type: - case internal::type::uint128_type: + case detail::type::int128_type: + case detail::type::uint128_type: break; #endif - case internal::type::bool_type: + case detail::type::bool_type: return vis(arg.value_.bool_value); - case internal::type::char_type: + case detail::type::char_type: return vis(arg.value_.char_value); - case internal::type::float_type: + case detail::type::float_type: return vis(arg.value_.float_value); - case internal::type::double_type: + case detail::type::double_type: return vis(arg.value_.double_value); - case internal::type::long_double_type: + case detail::type::long_double_type: return vis(arg.value_.long_double_value); - case internal::type::cstring_type: + case detail::type::cstring_type: return vis(arg.value_.string.data); - case internal::type::string_type: + case detail::type::string_type: return vis(basic_string_view(arg.value_.string.data, arg.value_.string.size)); - case internal::type::pointer_type: + case detail::type::pointer_type: return vis(arg.value_.pointer); - case internal::type::custom_type: + case detail::type::custom_type: return vis(typename basic_format_arg::handle(arg.value_.custom)); } return vis(monostate()); } -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; +// Checks whether T is a container with contiguous storage. +template struct is_contiguous : std::false_type {}; +template +struct is_contiguous> : std::true_type {}; +template +struct is_contiguous> : std::true_type {}; - struct entry { - basic_string_view name; - basic_format_arg arg; - }; +namespace detail { - entry* map_; - unsigned size_; +template +struct is_back_insert_iterator : std::false_type {}; +template +struct is_back_insert_iterator> + : std::true_type {}; - 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 {}; - } -}; +template +struct is_contiguous_back_insert_iterator : std::false_type {}; +template +struct is_contiguous_back_insert_iterator> + : is_contiguous {}; // A type-erased reference to an std::locale to avoid heavy include. class locale_ref { @@ -1194,23 +1253,30 @@ FMT_CONSTEXPR basic_format_arg make_arg(const T& value) { return arg; } -template inline value make_arg(const T& val) { return arg_mapper().map(val); } -template inline basic_format_arg 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 {}; +template const T& unwrap(const T& v) { return v; } +template const T& unwrap(const std::reference_wrapper& v) { + return static_cast(v); +} + 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 @@ -1235,14 +1301,14 @@ class dynamic_arg_list { public: template const T& push(const Arg& arg) { - auto next = std::move(head_); - auto node = new typed_node(arg); - head_.reset(node); - head_->next = std::move(next); - return node->value; + 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 +} // namespace detail // Formatting context. template class basic_format_context { @@ -1253,12 +1319,12 @@ template class basic_format_context { private: OutputIt out_; basic_format_args args_; - internal::arg_map map_; - internal::locale_ref loc_; + detail::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; @@ -1269,31 +1335,30 @@ template class basic_format_context { */ basic_format_context(OutputIt out, basic_format_args ctx_args, - internal::locale_ref loc = internal::locale_ref()) + detail::locale_ref loc = detail::locale_ref()) : out_(out), args_(ctx_args), loc_(loc) {} format_arg arg(int id) const { return args_.get(id); } + format_arg arg(basic_string_view name) { return args_.get(name); } + int arg_id(basic_string_view name) { return args_.get_id(name); } - // Checks if manual indexing is used and returns the argument with the - // specified name. - format_arg arg(basic_string_view name); - - internal::error_handler error_handler() { return {}; } + detail::error_handler error_handler() { return {}; } void on_error(const char* message) { error_handler().on_error(message); } // Returns an iterator to the beginning of the output range. iterator out() { return out_; } // Advances the begin iterator to ``it``. - void advance_to(iterator it) { out_ = it; } + void advance_to(iterator it) { + if (!detail::is_back_insert_iterator()) out_ = it; + } - internal::locale_ref locale() { return loc_; } + detail::locale_ref locale() { return loc_; } }; template using buffer_context = - basic_format_context>, - Char>; + basic_format_context>, Char>; using format_context = buffer_context; using wformat_context = buffer_context; @@ -1306,34 +1371,42 @@ 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 = detail::count_named_args(); + static const bool is_packed = num_args <= detail::max_packed_args; - using value_type = conditional_t, + 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)]; + detail::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 ? detail::encode_types() + : detail::is_unpacked_bit | num_args) | + (num_named_args != 0 + ? static_cast(detail::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)...} { + data_{detail::make_arg< + is_packed, Context, + detail::mapped_type_constant::value>(args)...} { + detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1353,8 +1426,24 @@ inline format_arg_store make_format_args( /** \rst - A dynamic version of `fmt::format_arg_store<>`. - It's equipped with a storage to potentially temporary objects which lifetime + Returns a named argument to be used in a formatting function. It should only + be used in a call to a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template +inline detail::named_arg arg(const Char* name, const T& arg) { + static_assert(!detail::is_named_arg(), "nested named arguments"); + return {name, arg}; +} + +/** + \rst + A dynamic version of `fmt::format_arg_store`. + It's equipped with a storage to potentially temporary objects which lifetimes could be shorter than the format arguments object. It can be implicitly converted into `~fmt::basic_format_args` for passing @@ -1363,7 +1452,7 @@ inline format_arg_store make_format_args( */ template class dynamic_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 @@ -1372,39 +1461,63 @@ class dynamic_format_arg_store using char_type = typename Context::char_type; template struct need_copy { - static constexpr internal::type mapped_type = - internal::mapped_type_constant::value; + static constexpr detail::type mapped_type = + detail::mapped_type_constant::value; enum { - value = !(internal::is_reference_wrapper::value || + value = !(detail::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)) + std::is_same>::value || + (mapped_type != detail::type::cstring_type && + mapped_type != detail::type::string_type && + mapped_type != detail::type::custom_type)) }; }; template - using stored_type = conditional_t::value, + using stored_type = conditional_t::value, std::basic_string, T>; // Storage of basic_format_arg must be contiguous. std::vector> data_; + std::vector> named_info_; // 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_; + detail::dynamic_arg_list dynamic_args_; friend class basic_format_args; unsigned long long get_types() const { - return internal::is_unpacked_bit | data_.size(); + return detail::is_unpacked_bit | data_.size() | + (named_info_.empty() + ? 0ULL + : static_cast(detail::has_named_args_bit)); + } + + const basic_format_arg* data() const { + return named_info_.empty() ? data_.data() : data_.data() + 1; } template void emplace_arg(const T& arg) { - data_.emplace_back(internal::make_arg(arg)); + data_.emplace_back(detail::make_arg(arg)); + } + + template + void emplace_arg(const detail::named_arg& arg) { + if (named_info_.empty()) { + constexpr const detail::named_arg_info* zero_ptr{nullptr}; + data_.insert(data_.begin(), {zero_ptr, 0}); + } + data_.emplace_back(detail::make_arg(detail::unwrap(arg.value))); + auto pop_one = [](std::vector>* data) { + data->pop_back(); + }; + std::unique_ptr>, decltype(pop_one)> + guard{&data_, pop_one}; + named_info_.push_back({arg.name, static_cast(data_.size() - 2u)}); + data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; + guard.release(); } public: @@ -1413,8 +1526,8 @@ class dynamic_format_arg_store 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). + Note that custom types and string types (but not string views) are copied + into the store dynamically allocating memory if necessary. **Example**:: @@ -1426,25 +1539,78 @@ class dynamic_format_arg_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)) + if (detail::const_check(need_copy::value)) emplace_arg(dynamic_args_.push>(arg)); else - emplace_arg(arg); + emplace_arg(detail::unwrap(arg)); } /** + \rst Adds a reference to the argument into the dynamic store for later passing to - a formating function. + a formating function. Supports named arguments wrapped in + ``std::reference_wrapper`` via ``std::ref()``/``std::cref()``. + + **Example**:: + + fmt::dynamic_format_arg_store store; + char str[] = "1234567890"; + store.push_back(std::cref(str)); + int a1_val{42}; + auto a1 = fmt::arg("a1_", a1_val); + store.push_back(std::cref(a1)); + + // Changing str affects the output but only for string and custom types. + str[0] = 'X'; + + std::string result = fmt::vformat("{} and {a1_}"); + assert(result == "X234567890 and 42"); + \endrst */ template void push_back(std::reference_wrapper arg) { static_assert( - need_copy::value, + detail::is_named_arg::type>::value || + need_copy::value, "objects of built-in types and string views are always copied"); emplace_arg(arg.get()); } + + /** + Adds named argument into the dynamic store for later passing to a formating + function. ``std::reference_wrapper`` is supported to avoid copying of the + argument. + */ + template + void push_back(const detail::named_arg& arg) { + const char_type* arg_name = + dynamic_args_.push>(arg.name).c_str(); + if (detail::const_check(need_copy::value)) { + emplace_arg( + fmt::arg(arg_name, dynamic_args_.push>(arg.value))); + } else { + emplace_arg(fmt::arg(arg_name, arg.value)); + } + } + + /** Erase all elements from the store */ + void clear() { + data_.clear(); + named_info_.clear(); + dynamic_args_ = internal::dynamic_arg_list(); + } + + /** + \rst + Reserves space to store at least *new_cap* arguments including + *new_cap_named* named arguments. + \endrst + */ + void reserve(size_t new_cap, size_t new_cap_named) { + FMT_ASSERT(new_cap >= new_cap_named, + "Set of arguments includes set of named arguments"); + data_.reserve(new_cap); + named_info_.reserve(new_cap_named); + } }; /** @@ -1463,49 +1629,40 @@ 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 detail::value* values_; const format_arg* args_; }; - bool is_packed() const { return (types_ & internal::is_unpacked_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); + bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; } + bool has_named_args() const { + return (desc_ & detail::has_named_args_bit) != 0; } - friend class internal::arg_map; - - void set_data(const internal::value* values) { values_ = values; } - void set_data(const format_arg* args) { 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]; - 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]; - return arg; + detail::type type(int index) const { + int shift = index * detail::packed_arg_bits; + unsigned int mask = (1 << detail::packed_arg_bits) - 1; + return static_cast((desc_ >> shift) & mask); } + basic_format_args(unsigned long long desc, + const detail::value* values) + : desc_(desc), values_(values) {} + basic_format_args(unsigned long long desc, const format_arg* args) + : desc_(desc), args_(args) {} + public: - basic_format_args() : types_(0) {} + basic_format_args() : desc_(0) {} /** \rst @@ -1513,10 +1670,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 @@ -1524,10 +1679,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()) {} /** \rst @@ -1535,22 +1688,42 @@ 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(detail::is_unpacked_bit | detail::to_unsigned(count), + args) {} - /** Returns the argument at specified index. */ - format_arg get(int index) const { - format_arg arg = do_get(index); - if (arg.type_ == internal::type::named_arg_type) - arg = arg.value_.named_arg->template deserialize(); + /** Returns the argument with the specified id. */ + format_arg get(int id) const { + format_arg arg; + if (!is_packed()) { + if (id < max_size()) arg = args_[id]; + return arg; + } + if (id >= detail::max_packed_args) return arg; + arg.type_ = type(id); + if (arg.type_ == detail::type::none_type) return arg; + arg.value_ = values_[id]; return arg; } + template format_arg get(basic_string_view name) const { + int id = get_id(name); + return id >= 0 ? get(id) : format_arg(); + } + + template int get_id(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 named_args.data[i].id; + } + return -1; + } + int max_size() const { - unsigned long long max_packed = internal::max_packed_args; + unsigned long long max_packed = detail::max_packed_args; return static_cast(is_packed() ? max_packed - : types_ & ~internal::is_unpacked_bit); + : desc_ & ~detail::is_unpacked_bit); } }; @@ -1558,59 +1731,18 @@ 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 {}; - -template -struct is_contiguous> : std::true_type {}; - -template -struct is_contiguous> : std::true_type {}; - -namespace internal { - -template -struct is_contiguous_back_insert_iterator : std::false_type {}; -template -struct is_contiguous_back_insert_iterator> - : is_contiguous {}; - -template struct named_arg_base { - basic_string_view name; - - // Serialized value. - mutable char data[sizeof(basic_format_arg>)]; - - named_arg_base(basic_string_view nm) : name(nm) {} - - template basic_format_arg deserialize() const { - basic_format_arg arg; - std::memcpy(&arg, data, sizeof(basic_format_arg)); - return arg; - } -}; - -struct view {}; - -template -struct named_arg : view, named_arg_base { - const T& value; - - named_arg(basic_string_view name, const T& val) - : named_arg_base(name), value(val) {} -}; +namespace detail { +// 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()."); @@ -1619,86 +1751,69 @@ 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...}; } -template +template ::value)> std::basic_string vformat( basic_string_view format_str, basic_format_args>> args); +std::string vformat(string_view format_str, format_args args); + template 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); -} // namespace internal - -/** - \rst - Returns a named argument to be used in a formatting function. It should only - be used in a call to a formatting function. - - **Example**:: - - 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, ""); - return {name, arg}; -} - -// Disable nested named arguments, e.g. ``arg("a", arg("b", 42))``. -template -void arg(S, internal::named_arg) = delete; +#ifndef _WIN32 +inline void vprint_mojibake(std::FILE*, string_view, format_args) {} +#endif +} // namespace detail /** 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. -template , - FMT_ENABLE_IF( - internal::is_contiguous_back_insert_iterator::value)> +template < + typename OutputIt, typename S, typename Char = char_t, + FMT_ENABLE_IF(detail::is_contiguous_back_insert_iterator::value)> 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))); - internal::vformat_to(buf, to_string_view(format_str), args); + auto& c = detail::get_container(out); + detail::container_buffer> buf(c); + detail::vformat_to(buf, to_string_view(format_str), args); return out; } template ::value&& internal::is_string::value)> + is_contiguous::value&& detail::is_string::value)> inline std::back_insert_iterator format_to( std::back_insert_iterator out, const S& format_str, Args&&... args) { return vformat_to(out, to_string_view(format_str), - internal::make_args_checked(format_str, args...)); + detail::make_args_checked(format_str, args...)); } 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); + return detail::vformat(to_string_view(format_str), args); } /** @@ -1714,10 +1829,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 = detail::make_args_checked(format_str, args...); + return detail::vformat(to_string_view(format_str), vargs); } FMT_API void vprint(string_view, format_args); @@ -1734,17 +1848,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 = detail::make_args_checked(format_str, args...); + return detail::is_unicode() + ? vprint(f, to_string_view(format_str), vargs) + : detail::vprint_mojibake(f, to_string_view(format_str), vargs); } /** @@ -1758,17 +1867,13 @@ 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 = detail::make_args_checked(format_str, args...); + return detail::is_unicode() + ? vprint(to_string_view(format_str), vargs) + : detail::vprint_mojibake(stdout, to_string_view(format_str), + vargs); } FMT_END_NAMESPACE diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 5d294bc7..d0080907 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -15,6 +15,7 @@ #include #include // for std::memmove #include +#include #include "format.h" #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) @@ -22,8 +23,14 @@ #endif #ifdef _WIN32 +# if defined(NOMINMAX) +# include +# else +# define NOMINMAX +# include +# undef NOMINMAX +# endif # include -# include #endif #ifdef _MSC_VER @@ -33,15 +40,17 @@ // Dummy implementations of strerror_r and strerror_s called if corresponding // system functions are not available. -inline fmt::internal::null<> strerror_r(int, char*, ...) { return {}; } -inline fmt::internal::null<> strerror_s(char*, std::size_t, ...) { return {}; } +inline fmt::detail::null<> strerror_r(int, char*, ...) { return {}; } +inline fmt::detail::null<> strerror_s(char*, size_t, ...) { return {}; } FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { FMT_FUNC void assert_fail(const char* file, int line, const char* message) { print(stderr, "{}:{}: assertion failed: {}", file, line, message); - std::abort(); + // Chosen instead of std::abort to satisfy Clang in CUDA mode during device + // code pass. + std::terminate(); } #ifndef _MSC_VER @@ -67,14 +76,14 @@ inline int fmt_snprintf(char* buffer, size_t size, const char* format, ...) { // other - failure // Buffer should be at least of size 1. FMT_FUNC int safe_strerror(int error_code, char*& buffer, - std::size_t buffer_size) FMT_NOEXCEPT { + size_t buffer_size) FMT_NOEXCEPT { FMT_ASSERT(buffer != nullptr && buffer_size != 0, "invalid buffer"); class dispatcher { private: int error_code_; char*& buffer_; - std::size_t buffer_size_; + size_t buffer_size_; // A noop assignment operator to avoid bogus warnings. void operator=(const dispatcher&) {} @@ -97,7 +106,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, // Handle the case when strerror_r is not available. FMT_MAYBE_UNUSED - int handle(internal::null<>) { + int handle(detail::null<>) { return fallback(strerror_s(buffer_, buffer_size_, error_code_)); } @@ -111,7 +120,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, #if !FMT_MSC_VER // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::null<>) { + int fallback(detail::null<>) { errno = 0; buffer_ = strerror(error_code_); return errno; @@ -119,7 +128,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, #endif public: - dispatcher(int err_code, char*& buf, std::size_t buf_size) + dispatcher(int err_code, char*& buf, size_t buf_size) : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} int run() { return handle(strerror_r(error_code_, buffer_, buffer_size_)); } @@ -127,7 +136,7 @@ FMT_FUNC int safe_strerror(int error_code, char*& buffer, return dispatcher(error_code, buffer, buffer_size).run(); } -FMT_FUNC void format_error_code(internal::buffer& out, int error_code, +FMT_FUNC void format_error_code(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { // Report error code making sure that the output fits into // inline_buffer_size to avoid dynamic memory allocation and potential @@ -136,20 +145,17 @@ FMT_FUNC void format_error_code(internal::buffer& out, int error_code, static const char SEP[] = ": "; static const char ERROR_STR[] = "error "; // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; auto abs_value = static_cast>(error_code); - if (internal::is_negative(error_code)) { + if (detail::is_negative(error_code)) { abs_value = 0 - abs_value; ++error_code_size; } - error_code_size += internal::to_unsigned(internal::count_digits(abs_value)); - internal::writer w(out); - if (message.size() <= inline_buffer_size - error_code_size) { - w.write(message); - w.write(SEP); - } - w.write(ERROR_STR); - w.write(error_code); + error_code_size += detail::to_unsigned(detail::count_digits(abs_value)); + auto it = std::back_inserter(out); + if (message.size() <= inline_buffer_size - error_code_size) + format_to(it, "{}{}", message, SEP); + format_to(it, "{}{}", ERROR_STR, error_code); assert(out.size() <= inline_buffer_size); } @@ -168,10 +174,10 @@ FMT_FUNC void fwrite_fully(const void* ptr, size_t size, size_t count, size_t written = std::fwrite(ptr, size, count, stream); if (written < count) FMT_THROW(system_error(errno, "cannot write to file")); } -} // namespace internal +} // namespace detail #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -namespace internal { +namespace detail { template locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { @@ -194,18 +200,16 @@ template FMT_FUNC Char decimal_point_impl(locale_ref loc) { return std::use_facet>(loc.get()) .decimal_point(); } -} // namespace internal +} // namespace detail #else template -FMT_FUNC std::string internal::grouping_impl(locale_ref) { +FMT_FUNC std::string detail::grouping_impl(locale_ref) { return "\03"; } -template -FMT_FUNC Char internal::thousands_sep_impl(locale_ref) { +template FMT_FUNC Char detail::thousands_sep_impl(locale_ref) { return FMT_STATIC_THOUSANDS_SEPARATOR; } -template -FMT_FUNC Char internal::decimal_point_impl(locale_ref) { +template FMT_FUNC Char detail::decimal_point_impl(locale_ref) { return '.'; } #endif @@ -222,9 +226,9 @@ FMT_FUNC void system_error::init(int err_code, string_view format_str, base = std::runtime_error(to_string(buffer)); } -namespace internal { +namespace detail { -template <> FMT_FUNC int count_digits<4>(internal::fallback_uintptr n) { +template <> FMT_FUNC int count_digits<4>(detail::fallback_uintptr n) { // fallback_uintptr is always stored in little endian. int i = static_cast(sizeof(void*)) - 1; while (i > 0 && n.value[i] == 0) --i; @@ -317,6 +321,10 @@ 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::left_padding_shifts[] = {31, 31, 0, 1, 0}; +template +const char basic_data::right_padding_shifts[] = {0, 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. @@ -573,9 +584,10 @@ class bigint { void operator=(const bigint&) = delete; void assign(const bigint& other) { - bigits_.resize(other.bigits_.size()); + auto size = other.bigits_.size(); + bigits_.resize(size); auto data = other.bigits_.data(); - std::copy(data, data + other.bigits_.size(), bigits_.data()); + std::copy(data, data + size, make_checked(bigits_.data(), size)); exp_ = other.exp_; } @@ -591,7 +603,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 +633,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 +648,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 +688,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 +698,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 +706,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 +720,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 +1035,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 +1154,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 +1169,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 +1187,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 +1216,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; } } @@ -1265,19 +1277,19 @@ FMT_FUNC const char* utf8_decode(const char* buf, uint32_t* c, int* e) { return next; } -} // namespace internal +} // namespace detail -template <> struct formatter { +template <> struct formatter { format_parse_context::iterator parse(format_parse_context& ctx) { return ctx.begin(); } - format_context::iterator format(const internal::bigint& n, + format_context::iterator format(const detail::bigint& n, format_context& ctx) { 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; @@ -1286,12 +1298,12 @@ template <> struct formatter { out = format_to(out, "{:08x}", value); } if (n.exp_ > 0) - out = format_to(out, "p{}", n.exp_ * internal::bigint::bigit_bits); + out = format_to(out, "p{}", n.exp_ * detail::bigint::bigit_bits); return out; } }; -FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { +FMT_FUNC detail::utf8_to_utf16::utf8_to_utf16(string_view s) { auto transcode = [this](const char* p) { auto cp = uint32_t(); auto error = 0; @@ -1313,7 +1325,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); @@ -1322,7 +1334,7 @@ FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { buffer_.push_back(0); } -FMT_FUNC void format_system_error(internal::buffer& out, int error_code, +FMT_FUNC void format_system_error(detail::buffer& out, int error_code, string_view message) FMT_NOEXCEPT { FMT_TRY { memory_buffer buf; @@ -1330,12 +1342,9 @@ FMT_FUNC void format_system_error(internal::buffer& out, int error_code, for (;;) { char* system_message = &buf[0]; int result = - internal::safe_strerror(error_code, system_message, buf.size()); + detail::safe_strerror(error_code, system_message, buf.size()); if (result == 0) { - internal::writer w(out); - w.write(message); - w.write(": "); - w.write(system_message); + format_to(std::back_inserter(out), "{}: {}", message, system_message); return; } if (result != ERANGE) @@ -1347,7 +1356,7 @@ FMT_FUNC void format_system_error(internal::buffer& out, int error_code, format_error_code(out, error_code, message); } -FMT_FUNC void internal::error_handler::on_error(const char* message) { +FMT_FUNC void detail::error_handler::on_error(const char* message) { FMT_THROW(format_error(message)); } @@ -1356,33 +1365,39 @@ FMT_FUNC void report_system_error(int error_code, report_error(format_system_error, error_code, message); } +FMT_FUNC std::string detail::vformat(string_view format_str, format_args args) { + memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + return to_string(buffer); +} + FMT_FUNC void vprint(std::FILE* f, string_view format_str, format_args args) { memory_buffer buffer; - internal::vformat_to(buffer, format_str, - basic_format_args>(args)); + detail::vformat_to(buffer, format_str, + basic_format_args>(args)); #ifdef _WIN32 auto fd = _fileno(f); if (_isatty(fd)) { - internal::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); + detail::utf8_to_utf16 u16(string_view(buffer.data(), buffer.size())); auto written = DWORD(); 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; } #endif - internal::fwrite_fully(buffer.data(), 1, buffer.size(), f); + detail::fwrite_fully(buffer.data(), 1, buffer.size(), f); } #ifdef _WIN32 // Print assuming legacy (non-Unicode) encoding. -FMT_FUNC void internal::vprint_mojibake(std::FILE* f, string_view format_str, - format_args args) { +FMT_FUNC void detail::vprint_mojibake(std::FILE* f, string_view format_str, + format_args args) { memory_buffer buffer; - internal::vformat_to(buffer, format_str, - basic_format_args>(args)); + detail::vformat_to(buffer, format_str, + basic_format_args>(args)); fwrite_fully(buffer.data(), 1, buffer.size(), f); } #endif diff --git a/include/fmt/format.h b/include/fmt/format.h index 36c30b9f..59fcf39f 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -63,6 +63,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]] @@ -78,20 +84,28 @@ # define FMT_FALLTHROUGH #endif +#ifndef FMT_MAYBE_UNUSED +# if FMT_HAS_CPP17_ATTRIBUTE(maybe_unused) +# define FMT_MAYBE_UNUSED [[maybe_unused]] +# else +# define FMT_MAYBE_UNUSED +# endif +#endif + #ifndef FMT_THROW # if FMT_EXCEPTIONS # if FMT_MSC_VER || FMT_NVCC FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { template inline void do_throw(const Exception& x) { // Silence unreachable code warnings in MSVC and NVCC because these // are nearly impossible to fix in a generic code. volatile bool b = true; if (b) throw x; } -} // namespace internal +} // namespace detail FMT_END_NAMESPACE -# define FMT_THROW(x) internal::do_throw(x) +# define FMT_THROW(x) detail::do_throw(x) # else # define FMT_THROW(x) throw x # endif @@ -129,8 +143,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 +151,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 @@ -154,7 +179,7 @@ FMT_END_NAMESPACE # include // _BitScanReverse, _BitScanReverse64 FMT_BEGIN_NAMESPACE -namespace internal { +namespace detail { // Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # ifndef __clang__ # pragma intrinsic(_BitScanReverse) @@ -167,10 +192,10 @@ 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) +# define FMT_BUILTIN_CLZ(n) detail::clz(n) # if defined(_WIN64) && !defined(__clang__) # pragma intrinsic(_BitScanReverse64) @@ -192,11 +217,11 @@ 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) -} // namespace internal +# define FMT_BUILTIN_CLZLL(n) detail::clzll(n) +} // namespace detail FMT_END_NAMESPACE #endif @@ -205,13 +230,8 @@ 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 { +namespace detail { // An equivalent of `*reinterpret_cast(&source)` that doesn't have // undefined behavior (e.g. due to type aliasing). @@ -268,9 +288,23 @@ template <> constexpr int num_bits() { std::numeric_limits::digits); } +FMT_INLINE void assume(bool condition) { + (void)condition; +#if FMT_HAS_BUILTIN(__builtin_assume) + __builtin_assume(condition); +#endif +} + +// A workaround for gcc 4.8 to make void_t work in a SFINAE context. +template struct void_t_impl { using type = void; }; + +template +using void_t = typename detail::void_t_impl::type; + // An approximation of iterator_t for pre-C++20 systems. template using iterator_t = decltype(std::begin(std::declval())); +template using sentinel_t = decltype(std::end(std::declval())); // Detect the iterator category of *any* given type in a SFINAE-friendly way. // Unfortunately, older implementations of std::iterator_traits are not safe @@ -317,25 +351,39 @@ inline typename Container::value_type* get_data(Container& c) { #if defined(_SECURE_SCL) && _SECURE_SCL // Make a checked iterator to avoid MSVC warnings. template using checked_ptr = stdext::checked_array_iterator; -template checked_ptr make_checked(T* p, std::size_t size) { +template checked_ptr make_checked(T* p, size_t size) { return {p, size}; } #else template using checked_ptr = T*; -template inline T* make_checked(T* p, std::size_t) { return p; } +template inline T* make_checked(T* p, size_t) { return p; } #endif template ::value)> -inline checked_ptr reserve( - std::back_insert_iterator& it, std::size_t n) { +#if FMT_CLANG_VERSION +__attribute__((no_sanitize("undefined"))) +#endif +inline checked_ptr +reserve(std::back_insert_iterator it, size_t n) { Container& c = get_container(it); - std::size_t size = c.size(); + size_t size = c.size(); c.resize(size + n); return make_checked(get_data(c) + size, n); } +template inline Iterator& reserve(Iterator& it, 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& reserve(Iterator& it, std::size_t) { +inline Iterator base_iterator(Iterator, Iterator it) { return it; } @@ -343,7 +391,7 @@ inline Iterator& reserve(Iterator& it, std::size_t) { // discards them. class counting_iterator { private: - std::size_t count_; + size_t count_; public: using iterator_category = std::output_iterator_tag; @@ -358,7 +406,7 @@ class counting_iterator { counting_iterator() : count_(0) {} - std::size_t count() const { return count_; } + size_t count() const { return count_; } counting_iterator& operator++() { ++count_; @@ -377,10 +425,10 @@ class counting_iterator { template class truncating_iterator_base { protected: OutputIt out_; - std::size_t limit_; - std::size_t count_; + size_t limit_; + size_t count_; - truncating_iterator_base(OutputIt out, std::size_t limit) + truncating_iterator_base(OutputIt out, size_t limit) : out_(out), limit_(limit), count_(0) {} public: @@ -393,7 +441,7 @@ template class truncating_iterator_base { truncating_iterator_base; // Mark iterator as checked. OutputIt base() const { return out_; } - std::size_t count() const { return count_; } + size_t count() const { return count_; } }; // An output iterator that truncates the output and counts the number of objects @@ -411,7 +459,7 @@ class truncating_iterator public: using value_type = typename truncating_iterator_base::value_type; - truncating_iterator(OutputIt out, std::size_t limit) + truncating_iterator(OutputIt out, size_t limit) : truncating_iterator_base(out, limit) {} truncating_iterator& operator++() { @@ -434,7 +482,7 @@ template class truncating_iterator : public truncating_iterator_base { public: - truncating_iterator(OutputIt out, std::size_t limit) + truncating_iterator(OutputIt out, size_t limit) : truncating_iterator_base(out, limit) {} template truncating_iterator& operator=(T val) { @@ -447,22 +495,6 @@ class truncating_iterator truncating_iterator& operator*() { return *this; } }; -// A range with the specified output iterator and value type. -template -class output_range { - private: - OutputIt it_; - - public: - using value_type = T; - using iterator = OutputIt; - struct sentinel {}; - - explicit output_range(OutputIt it) : it_(it) {} - OutputIt begin() const { return it_; } - sentinel end() const { return {}; } // Sentinel is not used yet. -}; - template inline size_t count_code_points(basic_string_view s) { return s.size(); @@ -501,8 +533,6 @@ inline size_t code_point_index(basic_string_view s, size_t n) { return s.size(); } -inline char8_type to_char8_t(char c) { return static_cast(c); } - template using needs_conversion = bool_constant< std::is_same::value_type, @@ -518,7 +548,8 @@ OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { template ::value)> OutputIt copy_str(InputIt begin, InputIt end, OutputIt it) { - return std::transform(begin, end, it, to_char8_t); + return std::transform(begin, end, it, + [](char c) { return static_cast(c); }); } #ifndef FMT_USE_GRISU @@ -533,43 +564,12 @@ template constexpr bool use_grisu() { template template void buffer::append(const U* begin, const U* end) { - std::size_t new_size = size_ + to_unsigned(end - begin); + size_t new_size = size_ + to_unsigned(end - begin); reserve(new_size); std::uninitialized_copy(begin, end, make_checked(ptr_, capacity_) + size_); size_ = new_size; } -} // namespace internal - -// A range with an iterator appending to a buffer. -template -class buffer_range : public internal::output_range< - std::back_insert_iterator>, T> { - public: - using iterator = std::back_insert_iterator>; - using internal::output_range::output_range; - buffer_range(internal::buffer& buf) - : internal::output_range(std::back_inserter(buf)) {} -}; - -class FMT_DEPRECATED u8string_view - : public basic_string_view { - public: - u8string_view(const char* s) - : basic_string_view( - reinterpret_cast(s)) {} - u8string_view(const char* s, size_t count) FMT_NOEXCEPT - : 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}; -} -} // namespace literals -#endif +} // namespace detail // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. @@ -604,27 +604,30 @@ enum { inline_buffer_size = 500 }; The output can be converted to an ``std::string`` with ``to_string(out)``. \endrst */ -template > -class basic_memory_buffer : private Allocator, public internal::buffer { +class basic_memory_buffer : public detail::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: - void grow(std::size_t size) FMT_OVERRIDE; + void grow(size_t size) FMT_OVERRIDE; public: using value_type = T; 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(); } @@ -632,14 +635,13 @@ 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(); + size_t size = other.size(), capacity = other.capacity(); if (data == other.store_) { this->set(store_, capacity); std::uninitialized_copy(other.store_, other.store_ + size, - internal::make_checked(store_, capacity)); + detail::make_checked(store_, capacity)); } else { this->set(data, capacity); // Set pointer to the inline array so that delete is not called @@ -671,27 +673,28 @@ 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"); +template +void basic_memory_buffer::grow(size_t size) { +#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; + size_t old_capacity = this->capacity(); + 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)); + detail::make_checked(new_data, new_capacity)); this->set(new_data, new_capacity); // 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; @@ -711,7 +714,7 @@ class FMT_API format_error : public std::runtime_error { ~format_error() FMT_NOEXCEPT FMT_OVERRIDE; }; -namespace internal { +namespace detail { // Returns true if value is negative, false otherwise. // Same as `value < 0` but doesn't produce warnings if T is an unsigned type. @@ -724,6 +727,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 @@ -745,9 +755,13 @@ 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 left_padding_shifts[5]; + static const char right_padding_shifts[5]; }; +#ifndef FMT_EXPORT FMT_EXTERN template struct basic_data; +#endif // This is a struct rather than an alias to avoid shadowing warnings in gcc. struct data : basic_data<> {}; @@ -805,7 +819,7 @@ template inline int count_digits(UInt n) { return num_digits; } -template <> int count_digits<4>(internal::fallback_uintptr n); +template <> int count_digits<4>(detail::fallback_uintptr n); #if FMT_GCC_VERSION || FMT_CLANG_VERSION # define FMT_ALWAYS_INLINE inline __attribute__((always_inline)) @@ -821,6 +835,12 @@ inline int count_digits(uint32_t n) { } #endif +template constexpr int digits10() FMT_NOEXCEPT { + return std::numeric_limits::digits10; +} +template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } +template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } + template FMT_API std::string grouping_impl(locale_ref loc); template inline std::string grouping(locale_ref loc) { return grouping_impl(loc); @@ -845,57 +865,51 @@ template <> inline wchar_t decimal_point(locale_ref loc) { return decimal_point_impl(loc); } -// Formats a decimal unsigned integer value writing into buffer. -// add_thousands_sep is called after writing each char to add a thousands -// separator if necessary. -template -inline Char* format_decimal(Char* buffer, UInt value, int num_digits, - F add_thousands_sep) { +// Compares two characters for equality. +template bool equal2(const Char* lhs, const char* rhs) { + return lhs[0] == rhs[0] && lhs[1] == rhs[1]; +} +inline bool equal2(const char* lhs, const char* rhs) { + return memcmp(lhs, rhs, 2) == 0; +} + +// Copies two characters from src to dst. +template void copy2(Char* dst, const char* src) { + *dst++ = static_cast(*src++); + *dst = static_cast(*src); +} +inline void copy2(char* dst, const char* src) { memcpy(dst, src, 2); } + +// Formats a decimal unsigned integer value writing into out. +template +inline Char* format_decimal(Char* out, UInt value, int num_digits) { FMT_ASSERT(num_digits >= 0, "invalid digit count"); - buffer += num_digits; - Char* end = buffer; + out += num_digits; + Char* end = out; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. - auto index = static_cast((value % 100) * 2); + out -= 2; + copy2(out, data::digits + static_cast((value % 100) * 2)); value /= 100; - *--buffer = static_cast(data::digits[index + 1]); - add_thousands_sep(buffer); - *--buffer = static_cast(data::digits[index]); - add_thousands_sep(buffer); } if (value < 10) { - *--buffer = static_cast('0' + value); + *--out = static_cast('0' + value); return end; } - auto index = static_cast(value * 2); - *--buffer = static_cast(data::digits[index + 1]); - add_thousands_sep(buffer); - *--buffer = static_cast(data::digits[index]); + copy2(out - 2, data::digits + static_cast(value * 2)); return end; } -template constexpr int digits10() FMT_NOEXCEPT { - return std::numeric_limits::digits10; -} -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } -template <> constexpr int digits10() FMT_NOEXCEPT { return 38; } - -template -inline Iterator format_decimal(Iterator out, UInt value, int num_digits, - F add_thousands_sep) { - FMT_ASSERT(num_digits >= 0, "invalid digit count"); +template >::value)> +inline Iterator format_decimal(Iterator out, UInt value, int num_digits) { // Buffer should be large enough to hold all digits (<= digits10 + 1). enum { max_size = digits10() + 1 }; Char buffer[2 * max_size]; - auto end = format_decimal(buffer, value, num_digits, add_thousands_sep); - return internal::copy_str(buffer, end, out); -} - -template -inline It format_decimal(It out, UInt value, int num_digits) { - return format_decimal(out, value, num_digits, [](Char*) {}); + auto end = format_decimal(buffer, value, num_digits); + return detail::copy_str(buffer, end, out); } template @@ -913,7 +927,7 @@ inline Char* format_uint(Char* buffer, UInt value, int num_digits, } template -Char* format_uint(Char* buffer, internal::fallback_uintptr n, int num_digits, +Char* format_uint(Char* buffer, detail::fallback_uintptr n, int num_digits, bool = false) { auto char_digits = std::numeric_limits::digits / 4; int start = (num_digits + char_digits - 1) / char_digits - 1; @@ -939,7 +953,7 @@ inline It format_uint(It out, UInt value, int num_digits, bool upper = false) { // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1). char buffer[num_bits() / BASE_BITS + 1]; format_uint(buffer, value, num_digits, upper); - return internal::copy_str(buffer, buffer + num_digits, out); + return detail::copy_str(buffer, buffer + num_digits, out); } // A converter from UTF-8 to UTF-16. @@ -990,7 +1004,7 @@ template struct fill_t { return fill; } }; -} // namespace internal +} // namespace detail // We cannot use enum classes as bit fields because of a gcc bug // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414. @@ -1012,7 +1026,7 @@ template struct basic_format_specs { align_t align : 4; sign_t sign : 3; bool alt : 1; // Alternate form ('#'). - internal::fill_t fill; + detail::fill_t fill; constexpr basic_format_specs() : width(0), @@ -1021,12 +1035,12 @@ template struct basic_format_specs { align(align::none), sign(sign::none), alt(false), - fill(internal::fill_t::make()) {} + fill(detail::fill_t::make()) {} }; using format_specs = basic_format_specs; -namespace internal { +namespace detail { // A floating-point presentation format. enum class float_format : unsigned char { @@ -1042,7 +1056,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; @@ -1105,8 +1118,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')); @@ -1133,9 +1146,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; @@ -1167,11 +1182,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); } }; @@ -1205,8 +1219,12 @@ FMT_CONSTEXPR void handle_int_type_spec(char spec, Handler&& handler) { handler.on_oct(); break; case 'n': + case 'L': handler.on_num(); break; + case 'c': + handler.on_chr(); + break; default: handler.on_error(); } @@ -1242,12 +1260,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; @@ -1255,6 +1267,7 @@ FMT_CONSTEXPR float_specs parse_float_type_spec( result.format = float_format::hex; break; case 'n': + case 'L': result.locale = true; break; default: @@ -1303,6 +1316,7 @@ template class int_type_checker : private ErrorHandler { FMT_CONSTEXPR void on_bin() {} FMT_CONSTEXPR void on_oct() {} FMT_CONSTEXPR void on_num() {} + FMT_CONSTEXPR void on_chr() {} FMT_CONSTEXPR void on_error() { ErrorHandler::on_error("invalid type specifier"); @@ -1334,104 +1348,350 @@ 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; } -// This template provides operations for formatting and writing data into a -// character range. -template class basic_writer { +// 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) { + static_assert(align == align::left || align == align::right, ""); + unsigned spec_width = to_unsigned(specs.width); + size_t padding = spec_width > width ? spec_width - width : 0; + auto* shifts = align == align::left ? data::left_padding_shifts + : data::right_padding_shifts; + size_t left_padding = 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); +} + +template +OutputIt write_bytes(OutputIt out, string_view bytes, + const basic_format_specs& specs) { + using iterator = remove_reference_t; + return write_padded(out, specs, bytes.size(), [bytes](iterator it) { + const char* data = bytes.data(); + return copy_str(data, data + bytes.size(), it); + }); +} + +// 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 { + size_t size; + size_t padding; + + write_int_data(int num_digits, string_view prefix, + const basic_format_specs& specs) + : size(prefix.size() + to_unsigned(num_digits)), padding(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); + } + } +}; + +// Writes an integer in the format +// +// where are written by f(it). +template +OutputIt write_int(OutputIt out, int num_digits, string_view prefix, + const 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, static_cast('0')); + return f(it); + }); +} + +template +OutputIt write(OutputIt out, basic_string_view s, + const basic_format_specs& specs = {}) { + auto data = s.data(); + auto size = s.size(); + if (specs.precision >= 0 && to_unsigned(specs.precision) < size) + size = code_point_index(s, to_unsigned(specs.precision)); + auto width = specs.width != 0 + ? count_code_points(basic_string_view(data, size)) + : 0; + using iterator = remove_reference_t; + return write_padded(out, specs, size, width, [=](iterator it) { + return copy_str(data, data + size, it); + }); +} + +// The handle_int_type_spec handler that writes an integer. +template struct int_writer { + OutputIt out; + locale_ref locale; + const basic_format_specs& specs; + UInt abs_value; + char prefix[4]; + unsigned prefix_size; + + using iterator = + remove_reference_t(), 0))>; + + string_view get_prefix() const { return string_view(prefix, prefix_size); } + + template + int_writer(OutputIt output, locale_ref loc, Int value, + const basic_format_specs& s) + : out(output), + locale(loc), + specs(s), + abs_value(static_cast(value)), + prefix_size(0) { + static_assert(std::is_same, UInt>::value, ""); + if (is_negative(value)) { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } else if (specs.sign != sign::none && specs.sign != sign::minus) { + prefix[0] = specs.sign == sign::plus ? '+' : ' '; + ++prefix_size; + } + } + + void on_dec() { + auto num_digits = count_digits(abs_value); + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_decimal(it, abs_value, num_digits); + }); + } + + void on_hex() { + if (specs.alt) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = specs.type; + } + int num_digits = count_digits<4>(abs_value); + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_uint<4, Char>(it, abs_value, num_digits, + specs.type != 'x'); + }); + } + + 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); + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_uint<1, Char>(it, abs_value, num_digits); + }); + } + + void on_oct() { + int num_digits = count_digits<3>(abs_value); + if (specs.alt && specs.precision <= num_digits && abs_value != 0) { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix[prefix_size++] = '0'; + } + out = write_int(out, num_digits, get_prefix(), specs, + [this, num_digits](iterator it) { + return format_uint<3, Char>(it, abs_value, num_digits); + }); + } + + enum { sep_size = 1 }; + + void on_num() { + std::string groups = grouping(locale); + if (groups.empty()) return on_dec(); + auto sep = thousands_sep(locale); + if (!sep) return on_dec(); + int num_digits = count_digits(abs_value); + int size = num_digits, n = num_digits; + std::string::const_iterator group = groups.cbegin(); + while (group != groups.cend() && num_digits > *group && *group > 0 && + *group != max_value()) { + size += sep_size; + n -= *group; + ++group; + } + if (group == groups.cend()) size += sep_size * ((n - 1) / groups.back()); + char digits[40]; + format_decimal(digits, abs_value, num_digits); + basic_memory_buffer buffer; + buffer.resize(size); + basic_string_view s(&sep, sep_size); + // Index of a decimal digit with the least significant digit having index 0. + int digit_index = 0; + group = groups.cbegin(); + auto p = buffer.data() + size; + for (int i = num_digits - 1; i >= 0; --i) { + *--p = static_cast(digits[i]); + if (*group <= 0 || ++digit_index % *group != 0 || + *group == max_value()) + continue; + if (group + 1 != groups.cend()) { + digit_index = 0; + ++group; + } + p -= s.size(); + std::uninitialized_copy(s.data(), s.data() + s.size(), + make_checked(p, s.size())); + } + write(out, basic_string_view(buffer.data(), buffer.size()), specs); + } + + void on_chr() { *out++ = static_cast(abs_value); } + + FMT_NORETURN void on_error() { + FMT_THROW(format_error("invalid type specifier")); + } +}; + +template +OutputIt write_nonfinite(OutputIt out, bool isinf, + const basic_format_specs& specs, + const float_specs& fspecs) { + auto str = + isinf ? (fspecs.upper ? "INF" : "inf") : (fspecs.upper ? "NAN" : "nan"); + constexpr size_t str_size = 3; + auto sign = fspecs.sign; + auto size = str_size + (sign ? 1 : 0); + using iterator = remove_reference_t; + return write_padded(out, specs, size, [=](iterator it) { + if (sign) *it++ = static_cast(data::signs[sign]); + return copy_str(str, str + str_size, it); + }); +} + +template ::value)> +OutputIt write(OutputIt out, T value, basic_format_specs specs = {}, + locale_ref loc = {}) { + if (const_check(!is_supported_floating_point(value))) return out; + float_specs fspecs = parse_float_type_spec(specs); + fspecs.sign = specs.sign; + if (std::signbit(value)) { // value < 0 is false for NaN so use signbit. + fspecs.sign = sign::minus; + value = -value; + } else if (fspecs.sign == sign::minus) { + fspecs.sign = sign::none; + } + + if (!std::isfinite(value)) + return write_nonfinite(out, std::isinf(value), specs, fspecs); + + if (specs.align == align::numeric && fspecs.sign) { + auto it = reserve(out, 1); + *it++ = static_cast(data::signs[fspecs.sign]); + out = base_iterator(out, it); + fspecs.sign = sign::none; + if (specs.width != 0) --specs.width; + } + + memory_buffer buffer; + if (fspecs.format == float_format::hex) { + if (fspecs.sign) buffer.push_back(data::signs[fspecs.sign]); + snprintf_float(promote_float(value), specs.precision, fspecs, buffer); + return write_bytes(out, {buffer.data(), buffer.size()}, specs); + } + int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; + if (fspecs.format == float_format::exp) { + if (precision == max_value()) + FMT_THROW(format_error("number is too big")); + else + ++precision; + } + if (const_check(std::is_same())) fspecs.binary32 = true; + fspecs.use_grisu = use_grisu(); + int exp = format_float(promote_float(value), precision, fspecs, buffer); + fspecs.precision = precision; + Char point = + fspecs.locale ? decimal_point(loc) : static_cast('.'); + float_writer w(buffer.data(), static_cast(buffer.size()), exp, + fspecs, point); + return write_padded(out, specs, w.size(), w); +} + +template +OutputIt write_char(OutputIt out, Char value, + const basic_format_specs& specs) { + using iterator = remove_reference_t; + return write_padded(out, specs, 1, [=](iterator it) { + *it++ = value; + return it; + }); +} + +template +OutputIt write_ptr(OutputIt out, UIntPtr value, + const basic_format_specs* specs) { + int num_digits = count_digits<4>(value); + auto size = to_unsigned(num_digits) + size_t(2); + using iterator = remove_reference_t; + auto write = [=](iterator it) { + *it++ = static_cast('0'); + *it++ = static_cast('x'); + return format_uint<4, Char>(it, value, num_digits); + }; + return specs ? write_padded(out, *specs, size, write) + : base_iterator(out, write(reserve(out, size))); +} + +template struct is_integral : std::is_integral {}; +template <> struct is_integral : std::true_type {}; +template <> struct is_integral : std::true_type {}; + +template +class arg_formatter_base { public: - using char_type = typename Range::value_type; - using iterator = typename Range::iterator; + using iterator = OutputIt; + using char_type = Char; using format_specs = basic_format_specs; private: iterator out_; // Output iterator. locale_ref locale_; + format_specs* specs_; // Attempts to reserve space for n extra characters in the output range. // Returns a pointer to the reserved range or a reference to out_. - auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { - return internal::reserve(out_, n); + auto reserve(size_t n) -> decltype(detail::reserve(out_, n)) { + return detail::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}); - } + using reserve_iterator = remove_reference_t(), 0))>; // Writes a decimal integer. template void write_decimal(Int value) { @@ -1445,226 +1705,6 @@ template class basic_writer { it = format_decimal(it, abs_value, num_digits); } - // The handle_int_type_spec handler that writes an integer. - template struct int_writer { - using unsigned_type = uint32_or_64_or_128_t; - - basic_writer& writer; - const Specs& specs; - unsigned_type 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) - : writer(w), - specs(s), - abs_value(static_cast(value)), - prefix_size(0) { - if (is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (specs.sign != sign::none && specs.sign != sign::minus) { - prefix[0] = specs.sign == sign::plus ? '+' : ' '; - ++prefix_size; - } - } - - 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}); - } - - 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}); - } - - 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}); - } - - void on_oct() { - int num_digits = count_digits<3>(abs_value); - if (specs.alt && specs.precision <= num_digits && abs_value != 0) { - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - prefix[prefix_size++] = '0'; - } - writer.write_int(num_digits, get_prefix(), specs, - bin_writer<3>{abs_value, num_digits}); - } - - enum { sep_size = 1 }; - - struct num_writer { - unsigned_type abs_value; - int size; - const std::string& groups; - char_type sep; - - template void 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( - it, abs_value, size, - [this, s, &group, &digit_index](char_type*& buffer) { - if (*group <= 0 || ++digit_index % *group != 0 || - *group == max_value()) - return; - if (group + 1 != groups.cend()) { - digit_index = 0; - ++group; - } - buffer -= s.size(); - std::uninitialized_copy(s.data(), s.data() + s.size(), - make_checked(buffer, s.size())); - }); - } - }; - - void on_num() { - std::string groups = grouping(writer.locale_); - if (groups.empty()) return on_dec(); - auto sep = thousands_sep(writer.locale_); - if (!sep) return on_dec(); - int num_digits = count_digits(abs_value); - int size = num_digits; - std::string::const_iterator group = groups.cbegin(); - while (group != groups.cend() && num_digits > *group && *group > 0 && - *group != max_value()) { - size += sep_size; - num_digits -= *group; - ++group; - } - 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}); - } - - FMT_NORETURN void on_error() { - FMT_THROW(format_error("invalid type specifier")); - } - }; - - 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); - } - }; - - 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); - } - } - void write(int value) { write_decimal(value); } void write(long value) { write_decimal(value); } void write(long long value) { write_decimal(value); } @@ -1678,68 +1718,11 @@ 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 ::value)> - void write(T value, format_specs specs = {}) { - 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. - fspecs.sign = sign::minus; - value = -value; - } else if (fspecs.sign == sign::minus) { - fspecs.sign = sign::none; - } - - 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}); - } - - if (specs.align == align::none) { - specs.align = align::right; - } else if (specs.align == align::numeric) { - if (fspecs.sign) { - auto&& it = reserve(1); - *it++ = static_cast(data::signs[fspecs.sign]); - fspecs.sign = sign::none; - if (specs.width != 0) --specs.width; - } - specs.align = align::right; - } - - memory_buffer buffer; - 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; - } - int precision = specs.precision >= 0 || !specs.type ? specs.precision : 6; - if (fspecs.format == float_format::exp) { - if (precision == max_value()) - FMT_THROW(format_error("number is too big")); - else - ++precision; - } - 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)); + template void write_int(T value, const format_specs& spec) { + using uint_type = uint32_or_64_or_128_t; + int_writer w(out_, locale_, value, spec); + handle_int_type_spec(spec.type, w); + out_ = w.out; } void write(char value) { @@ -1747,8 +1730,8 @@ template class basic_writer { *it++ = value; } - template ::value)> - void write(Char value) { + template ::value)> + void write(Ch value) { auto&& it = reserve(1); *it++ = value; } @@ -1763,82 +1746,32 @@ template class basic_writer { it = std::copy(value.begin(), value.end(), it); } - template - void write(const Char* s, std::size_t size, const format_specs& specs) { - write_padded(specs, str_writer{s, size}); + template + void write(const Ch* s, size_t size, const format_specs& specs) { + 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 - void write(basic_string_view s, const format_specs& specs = {}) { - const Char* data = s.data(); - std::size_t size = s.size(); - if (specs.precision >= 0 && to_unsigned(specs.precision) < size) - size = code_point_index(s, to_unsigned(specs.precision)); - write(data, size, specs); - } - - void write_bytes(string_view bytes, const format_specs& specs) { - write_padded(specs, bytes_writer{bytes}); - } - - 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)); - format_specs specs_copy = *specs; - if (specs_copy.align == align::none) specs_copy.align = align::right; - write_padded(specs_copy, pw); - } -}; - -using writer = basic_writer>; - -template struct is_integral : std::is_integral {}; -template <> struct is_integral : std::true_type {}; -template <> struct is_integral : std::true_type {}; - -template -class arg_formatter_base { - public: - using char_type = typename Range::value_type; - using iterator = typename Range::iterator; - using format_specs = basic_format_specs; - - private: - using writer_type = basic_writer; - writer_type writer_; - format_specs* specs_; - - struct char_writer { - char_type value; - - size_t size() const { return 1; } - size_t width() const { return 1; } - - template void operator()(It&& it) const { *it++ = value; } - }; - - void write_char(char_type value) { - if (specs_) - writer_.write_padded(*specs_, char_writer{value}); - else - writer_.write(value); + template + void write(basic_string_view s, const format_specs& specs = {}) { + out_ = detail::write(out_, s, specs); } void write_pointer(const void* p) { - writer_.write_pointer(internal::to_uintptr(p), specs_); + out_ = write_ptr(out_, detail::to_uintptr(p), specs_); } protected: - writer_type& writer() { return writer_; } - FMT_DEPRECATED format_specs* spec() { return specs_; } + iterator out() { return out_; } format_specs* specs() { return specs_; } - iterator out() { return writer_.out(); } void write(bool value) { string_view sv(value ? "true" : "false"); - specs_ ? writer_.write(sv, *specs_) : writer_.write(sv); + specs_ ? write(sv, *specs_) : write(sv); } void write(const char_type* value) { @@ -1847,13 +1780,13 @@ class arg_formatter_base { } else { auto length = std::char_traits::length(value); basic_string_view sv(value, length); - specs_ ? writer_.write(sv, *specs_) : writer_.write(sv); + specs_ ? write(sv, *specs_) : write(sv); } } public: - arg_formatter_base(Range r, format_specs* s, locale_ref loc) - : writer_(r, loc), specs_(s) {} + arg_formatter_base(OutputIt out, format_specs* s, locale_ref loc) + : out_(out), locale_(loc), specs_(s) {} iterator operator()(monostate) { FMT_ASSERT(false, "invalid argument type"); @@ -1861,16 +1794,16 @@ class arg_formatter_base { } template ::value)> - iterator operator()(T value) { + FMT_INLINE iterator operator()(T value) { if (specs_) - writer_.write_int(value, *specs_); + write_int(value, *specs_); else - writer_.write(value); + write(value); return out(); } iterator operator()(char_type value) { - internal::handle_char_specs( + detail::handle_char_specs( specs_, char_spec_handler(*this, static_cast(value))); return out(); } @@ -1883,7 +1816,11 @@ class arg_formatter_base { template ::value)> iterator operator()(T value) { - writer_.write(value, specs_ ? *specs_ : format_specs()); + auto specs = specs_ ? *specs_ : format_specs(); + if (const_check(is_supported_floating_point(value))) + out_ = detail::write(out_, value, specs, locale_); + else + FMT_ASSERT(false, "unsupported float argument type"); return out(); } @@ -1896,14 +1833,19 @@ class arg_formatter_base { void on_int() { if (formatter.specs_) - formatter.writer_.write_int(value, *formatter.specs_); + formatter.write_int(static_cast(value), *formatter.specs_); else - formatter.writer_.write(value); + formatter.write(value); + } + void on_char() { + if (formatter.specs_) + formatter.out_ = write_char(formatter.out_, value, *formatter.specs_); + else + formatter.write(value); } - void on_char() { formatter.write_char(value); } }; - struct cstring_spec_handler : internal::error_handler { + struct cstring_spec_handler : detail::error_handler { arg_formatter_base& formatter; const char_type* value; @@ -1916,24 +1858,23 @@ class arg_formatter_base { iterator operator()(const char_type* value) { if (!specs_) return write(value), out(); - internal::handle_cstring_type_spec(specs_->type, - cstring_spec_handler(*this, value)); + detail::handle_cstring_type_spec(specs_->type, + cstring_spec_handler(*this, value)); return out(); } iterator operator()(basic_string_view value) { if (specs_) { - internal::check_string_type_spec(specs_->type, internal::error_handler()); - writer_.write(value, *specs_); + detail::check_string_type_spec(specs_->type, detail::error_handler()); + write(value, *specs_); } else { - writer_.write(value); + write(value); } return out(); } iterator operator()(const void* value) { - if (specs_) - check_pointer_type_spec(specs_->type, internal::error_handler()); + if (specs_) check_pointer_type_spec(specs_->type, detail::error_handler()); write_pointer(value); return out(); } @@ -2071,7 +2012,7 @@ template class specs_setter { template class numeric_specs_checker { public: - FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, internal::type arg_type) + FMT_CONSTEXPR numeric_specs_checker(ErrorHandler& eh, detail::type arg_type) : error_handler_(eh), arg_type_(arg_type) {} FMT_CONSTEXPR void require_numeric_argument() { @@ -2094,18 +2035,24 @@ template class numeric_specs_checker { private: ErrorHandler& error_handler_; - internal::type arg_type_; + detail::type arg_type_; }; // 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) {} + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type 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(); @@ -2138,9 +2085,6 @@ template class specs_checker : public Handler { } FMT_CONSTEXPR void end_precision() { checker_.check_precision(); } - - private: - numeric_specs_checker checker_; }; template