diff options
Diffstat (limited to 'subprojects/fmt/include/fmt/core.h')
-rw-r--r-- | subprojects/fmt/include/fmt/core.h | 3031 |
1 files changed, 2093 insertions, 938 deletions
diff --git a/subprojects/fmt/include/fmt/core.h b/subprojects/fmt/include/fmt/core.h index 0a81e0c..a3096d5 100644 --- a/subprojects/fmt/include/fmt/core.h +++ b/subprojects/fmt/include/fmt/core.h @@ -1,4 +1,4 @@ -// Formatting library for C++ - the core API +// Formatting library for C++ - the core API for char/UTF-8 // // Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. @@ -8,54 +8,59 @@ #ifndef FMT_CORE_H_ #define FMT_CORE_H_ -#include <cstdio> // std::FILE -#include <cstring> -#include <functional> +#include <cstddef> // std::byte +#include <cstdio> // std::FILE +#include <cstring> // std::strlen #include <iterator> -#include <memory> +#include <limits> #include <string> #include <type_traits> -#include <vector> // The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 70103 +#define FMT_VERSION 90000 -#ifdef __clang__ +#if defined(__clang__) && !defined(__ibmxl__) # define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else # define FMT_CLANG_VERSION 0 #endif -#if defined(__GNUC__) && !defined(__clang__) +#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && \ + !defined(__NVCOMPILER) # 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 +#ifndef FMT_GCC_PRAGMA +// Workaround _Pragma bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59884. +# if FMT_GCC_VERSION >= 504 +# define FMT_GCC_PRAGMA(arg) _Pragma(arg) +# else +# define FMT_GCC_PRAGMA(arg) +# endif #endif -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#ifdef __ICL +# define FMT_ICC_VERSION __ICL +#elif defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER #else -# define FMT_HAS_GXX_CXX11 0 +# define FMT_ICC_VERSION 0 #endif -#ifdef __NVCC__ -# define FMT_NVCC __NVCC__ +#ifdef _MSC_VER +# define FMT_MSC_VERSION _MSC_VER +# define FMT_MSC_WARNING(...) __pragma(warning(__VA_ARGS__)) #else -# define FMT_NVCC 0 +# define FMT_MSC_VERSION 0 +# define FMT_MSC_WARNING(...) #endif -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER -# define FMT_SUPPRESS_MSC_WARNING(n) __pragma(warning(suppress : n)) +#ifdef _MSVC_LANG +# define FMT_CPLUSPLUS _MSVC_LANG #else -# define FMT_MSC_VER 0 -# define FMT_SUPPRESS_MSC_WARNING(n) +# define FMT_CPLUSPLUS __cplusplus #endif #ifdef __has_feature @@ -64,8 +69,9 @@ # define FMT_HAS_FEATURE(x) 0 #endif -#if defined(__has_include) && !defined(__INTELLISENSE__) && \ - (!FMT_ICC_VERSION || FMT_ICC_VERSION >= 1600) +#if (defined(__has_include) || FMT_ICC_VERSION >= 1600 || \ + FMT_MSC_VERSION > 1900) && \ + !defined(__INTELLISENSE__) # define FMT_HAS_INCLUDE(x) __has_include(x) #else # define FMT_HAS_INCLUDE(x) 0 @@ -78,96 +84,112 @@ #endif #define FMT_HAS_CPP14_ATTRIBUTE(attribute) \ - (__cplusplus >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + (FMT_CPLUSPLUS >= 201402L && FMT_HAS_CPP_ATTRIBUTE(attribute)) #define FMT_HAS_CPP17_ATTRIBUTE(attribute) \ - (__cplusplus >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) + (FMT_CPLUSPLUS >= 201703L && FMT_HAS_CPP_ATTRIBUTE(attribute)) // Check if relaxed C++14 constexpr is supported. // GCC doesn't allow throw in constexpr until version 6 (bug 67371). #ifndef FMT_USE_CONSTEXPR -# define FMT_USE_CONSTEXPR \ - (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ - (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) && \ - !FMT_NVCC && !FMT_ICC_VERSION +# if (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VERSION >= 1912 || \ + (FMT_GCC_VERSION >= 600 && FMT_CPLUSPLUS >= 201402L)) && \ + !FMT_ICC_VERSION && !defined(__NVCC__) +# define FMT_USE_CONSTEXPR 1 +# else +# define FMT_USE_CONSTEXPR 0 +# endif #endif #if FMT_USE_CONSTEXPR # define FMT_CONSTEXPR constexpr -# define FMT_CONSTEXPR_DECL constexpr #else -# define FMT_CONSTEXPR inline -# define FMT_CONSTEXPR_DECL +# define FMT_CONSTEXPR #endif -#ifndef FMT_OVERRIDE -# if FMT_HAS_FEATURE(cxx_override_control) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE +#if ((FMT_CPLUSPLUS >= 202002L) && \ + (!defined(_GLIBCXX_RELEASE) || _GLIBCXX_RELEASE > 9)) || \ + (FMT_CPLUSPLUS >= 201709L && FMT_GCC_VERSION >= 1002) +# define FMT_CONSTEXPR20 constexpr +#else +# define FMT_CONSTEXPR20 +#endif + +// Check if constexpr std::char_traits<>::{compare,length} are supported. +#if defined(__GLIBCXX__) +# if FMT_CPLUSPLUS >= 201703L && defined(_GLIBCXX_RELEASE) && \ + _GLIBCXX_RELEASE >= 7 // GCC 7+ libstdc++ has _GLIBCXX_RELEASE. +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr # endif +#elif defined(_LIBCPP_VERSION) && FMT_CPLUSPLUS >= 201703L && \ + _LIBCPP_VERSION >= 4000 +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +#elif FMT_MSC_VERSION >= 1914 && FMT_CPLUSPLUS >= 201703L +# define FMT_CONSTEXPR_CHAR_TRAITS constexpr +#endif +#ifndef FMT_CONSTEXPR_CHAR_TRAITS +# define FMT_CONSTEXPR_CHAR_TRAITS #endif // Check if exceptions are disabled. #ifndef FMT_EXCEPTIONS # if (defined(__GNUC__) && !defined(__EXCEPTIONS)) || \ - FMT_MSC_VER && !_HAS_EXCEPTIONS + (FMT_MSC_VERSION && !_HAS_EXCEPTIONS) # define FMT_EXCEPTIONS 0 # else # define FMT_EXCEPTIONS 1 # endif #endif -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 -#endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept -# define FMT_HAS_CXX11_NOEXCEPT 1 -#else -# define FMT_DETECTED_NOEXCEPT throw() -# define FMT_HAS_CXX11_NOEXCEPT 0 -#endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +#ifndef FMT_DEPRECATED +# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VERSION >= 1900 +# define FMT_DEPRECATED [[deprecated]] # else -# define FMT_NOEXCEPT +# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) +# define FMT_DEPRECATED __attribute__((deprecated)) +# elif FMT_MSC_VERSION +# define FMT_DEPRECATED __declspec(deprecated) +# else +# define FMT_DEPRECATED /* deprecated */ +# endif # endif #endif // [[noreturn]] is disabled on MSVC and NVCC because of bogus unreachable code // warnings. -#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VER && \ - !FMT_NVCC +#if FMT_EXCEPTIONS && FMT_HAS_CPP_ATTRIBUTE(noreturn) && !FMT_MSC_VERSION && \ + !defined(__NVCC__) # define FMT_NORETURN [[noreturn]] #else # define FMT_NORETURN #endif -#ifndef FMT_DEPRECATED -# if FMT_HAS_CPP14_ATTRIBUTE(deprecated) || FMT_MSC_VER >= 1900 -# define FMT_DEPRECATED [[deprecated]] +#if FMT_HAS_CPP17_ATTRIBUTE(fallthrough) +# define FMT_FALLTHROUGH [[fallthrough]] +#elif defined(__clang__) +# define FMT_FALLTHROUGH [[clang::fallthrough]] +#elif FMT_GCC_VERSION >= 700 && \ + (!defined(__EDG_VERSION__) || __EDG_VERSION__ >= 520) +# define FMT_FALLTHROUGH [[gnu::fallthrough]] +#else +# define FMT_FALLTHROUGH +#endif + +#ifndef FMT_NODISCARD +# if FMT_HAS_CPP17_ATTRIBUTE(nodiscard) +# define FMT_NODISCARD [[nodiscard]] # else -# if (defined(__GNUC__) && !defined(__LCC__)) || defined(__clang__) -# define FMT_DEPRECATED __attribute__((deprecated)) -# elif FMT_MSC_VER -# define FMT_DEPRECATED __declspec(deprecated) -# else -# define FMT_DEPRECATED /* deprecated */ -# endif +# define FMT_NODISCARD # endif #endif -// Workaround broken [[deprecated]] in the Intel, PGI and NVCC compilers. -#if FMT_ICC_VERSION || defined(__PGI) || FMT_NVCC -# define FMT_DEPRECATED_ALIAS -#else -# define FMT_DEPRECATED_ALIAS FMT_DEPRECATED +#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 #ifndef FMT_INLINE @@ -178,86 +200,96 @@ # endif #endif -#ifndef FMT_USE_INLINE_NAMESPACES -# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ - (FMT_MSC_VER >= 1900 && !_MANAGED) -# define FMT_USE_INLINE_NAMESPACES 1 -# else -# define FMT_USE_INLINE_NAMESPACES 0 -# endif +#ifdef _MSC_VER +# define FMT_UNCHECKED_ITERATOR(It) \ + using _Unchecked_type = It // Mark iterator as checked. +#else +# define FMT_UNCHECKED_ITERATOR(It) using unchecked_type = It #endif #ifndef FMT_BEGIN_NAMESPACE -# if FMT_USE_INLINE_NAMESPACES -# define FMT_INLINE_NAMESPACE inline namespace -# define FMT_END_NAMESPACE \ - } \ - } -# else -# define FMT_INLINE_NAMESPACE namespace -# define FMT_END_NAMESPACE \ - } \ - using namespace v7; \ - } -# endif # define FMT_BEGIN_NAMESPACE \ namespace fmt { \ - FMT_INLINE_NAMESPACE v7 { + inline namespace v9 { +# define FMT_END_NAMESPACE \ + } \ + } +#endif + +#ifndef FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT +# define FMT_MODULE_EXPORT_BEGIN +# define FMT_MODULE_EXPORT_END +# define FMT_BEGIN_DETAIL_NAMESPACE namespace detail { +# define FMT_END_DETAIL_NAMESPACE } #endif #if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# define FMT_CLASS_API FMT_SUPPRESS_MSC_WARNING(4275) +# define FMT_CLASS_API FMT_MSC_WARNING(suppress : 4275) # ifdef FMT_EXPORT # define FMT_API __declspec(dllexport) -# define FMT_EXTERN_TEMPLATE_API FMT_API -# define FMT_EXPORTED # elif defined(FMT_SHARED) # define FMT_API __declspec(dllimport) -# define FMT_EXTERN_TEMPLATE_API FMT_API # endif #else # define FMT_CLASS_API +# if defined(FMT_EXPORT) || defined(FMT_SHARED) +# if defined(__GNUC__) || defined(__clang__) +# define FMT_API __attribute__((visibility("default"))) +# endif +# endif #endif #ifndef FMT_API # define FMT_API #endif -#ifndef FMT_EXTERN_TEMPLATE_API -# define FMT_EXTERN_TEMPLATE_API -#endif -#ifndef FMT_INSTANTIATION_DEF_API -# define FMT_INSTANTIATION_DEF_API FMT_API -#endif - -#ifndef FMT_HEADER_ONLY -# define FMT_EXTERN extern -#else -# define FMT_EXTERN -#endif // libc++ supports string_view in pre-c++17. -#if (FMT_HAS_INCLUDE(<string_view>) && \ - (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ - (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +#if FMT_HAS_INCLUDE(<string_view>) && \ + (FMT_CPLUSPLUS >= 201703L || defined(_LIBCPP_VERSION)) # include <string_view> # define FMT_USE_STRING_VIEW -#elif FMT_HAS_INCLUDE("experimental/string_view") && __cplusplus >= 201402L +#elif FMT_HAS_INCLUDE("experimental/string_view") && FMT_CPLUSPLUS >= 201402L # include <experimental/string_view> # define FMT_USE_EXPERIMENTAL_STRING_VIEW #endif #ifndef FMT_UNICODE -# define FMT_UNICODE !FMT_MSC_VER +# define FMT_UNICODE !FMT_MSC_VERSION +#endif + +#ifndef FMT_CONSTEVAL +# if ((FMT_GCC_VERSION >= 1000 || FMT_CLANG_VERSION >= 1101) && \ + FMT_CPLUSPLUS >= 202002L && !defined(__apple_build_version__)) || \ + (defined(__cpp_consteval) && \ + (!FMT_MSC_VERSION || _MSC_FULL_VER >= 193030704)) +// consteval is broken in MSVC before VS2022 and Apple clang 13. +# define FMT_CONSTEVAL consteval +# define FMT_HAS_CONSTEVAL +# else +# define FMT_CONSTEVAL +# endif #endif -#if FMT_UNICODE && FMT_MSC_VER -# pragma execution_character_set("utf-8") + +#ifndef FMT_USE_NONTYPE_TEMPLATE_ARGS +# if defined(__cpp_nontype_template_args) && \ + ((FMT_GCC_VERSION >= 903 && FMT_CPLUSPLUS >= 201709L) || \ + __cpp_nontype_template_args >= 201911L) +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 1 +# else +# define FMT_USE_NONTYPE_TEMPLATE_ARGS 0 +# endif #endif +// Enable minimal optimizations for more compact code in debug mode. +FMT_GCC_PRAGMA("GCC push_options") + FMT_BEGIN_NAMESPACE +FMT_MODULE_EXPORT_BEGIN // Implementations of enable_if_t and other metafunctions for older systems. -template <bool B, class T = void> +template <bool B, typename T = void> using enable_if_t = typename std::enable_if<B, T>::type; -template <bool B, class T, class F> +template <bool B, typename T, typename F> using conditional_t = typename std::conditional<B, T, F>::type; template <bool B> using bool_constant = std::integral_constant<bool, B>; template <typename T> @@ -268,26 +300,64 @@ template <typename T> using remove_cvref_t = typename std::remove_cv<remove_reference_t<T>>::type; template <typename T> struct type_identity { using type = T; }; template <typename T> using type_identity_t = typename type_identity<T>::type; - -struct monostate {}; +template <typename T> +using underlying_t = typename std::underlying_type<T>::type; + +template <typename...> struct disjunction : std::false_type {}; +template <typename P> struct disjunction<P> : P {}; +template <typename P1, typename... Pn> +struct disjunction<P1, Pn...> + : conditional_t<bool(P1::value), P1, disjunction<Pn...>> {}; + +template <typename...> struct conjunction : std::true_type {}; +template <typename P> struct conjunction<P> : P {}; +template <typename P1, typename... Pn> +struct conjunction<P1, Pn...> + : conditional_t<bool(P1::value), conjunction<Pn...>, P1> {}; + +struct monostate { + constexpr monostate() {} +}; // An enable_if helper to be used in template parameters which results in much // shorter symbols: https://godbolt.org/z/sWw4vP. Extra parentheses are needed // to workaround a bug in MSVC 2019 (see #1140 and #1186). -#define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +#ifdef FMT_DOC +# define FMT_ENABLE_IF(...) +#else +# define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 +#endif + +FMT_BEGIN_DETAIL_NAMESPACE -namespace detail { +// Suppresses "unused variable" warnings with the method described in +// https://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/. +// (void)var does not work on many Intel compilers. +template <typename... T> FMT_CONSTEXPR void ignore_unused(const T&...) {} -// A helper function to suppress "conditional expression is constant" warnings. -template <typename T> constexpr T const_check(T value) { return value; } +constexpr FMT_INLINE auto is_constant_evaluated( + bool default_value = false) noexcept -> bool { +#ifdef __cpp_lib_is_constant_evaluated + ignore_unused(default_value); + return std::is_constant_evaluated(); +#else + return default_value; +#endif +} + +// Suppresses "conditional expression is constant" warnings. +template <typename T> constexpr FMT_INLINE auto const_check(T value) -> T { + return value; +} FMT_NORETURN FMT_API void assert_fail(const char* file, int line, const char* message); #ifndef FMT_ASSERT # ifdef NDEBUG -// FMT_ASSERT is not empty to avoid -Werror=empty-body. -# define FMT_ASSERT(condition, message) ((void)0) +// FMT_ASSERT is not empty to avoid -Wempty-body. +# define FMT_ASSERT(condition, message) \ + ::fmt::detail::ignore_unused((condition), (message)) # else # define FMT_ASSERT(condition, message) \ ((condition) /* void() fails with -Winvalid-constexpr on clang 4.0.1 */ \ @@ -307,43 +377,41 @@ template <typename T> struct std_string_view {}; #ifdef FMT_USE_INT128 // Do nothing. -#elif defined(__SIZEOF_INT128__) && !FMT_NVCC && \ - !(FMT_CLANG_VERSION && FMT_MSC_VER) +#elif defined(__SIZEOF_INT128__) && !defined(__NVCC__) && \ + !(FMT_CLANG_VERSION && FMT_MSC_VERSION) # define FMT_USE_INT128 1 -using int128_t = __int128_t; -using uint128_t = __uint128_t; +using int128_opt = __int128_t; // An optional native 128-bit integer. +using uint128_opt = __uint128_t; +template <typename T> inline auto convert_for_visit(T value) -> T { + return value; +} #else # define FMT_USE_INT128 0 #endif #if !FMT_USE_INT128 -struct int128_t {}; -struct uint128_t {}; +enum class int128_opt {}; +enum class uint128_opt {}; +// Reduce template instantiations. +template <typename T> auto convert_for_visit(T) -> monostate { return {}; } #endif // Casts a nonnegative integer to unsigned. template <typename Int> -FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) { +FMT_CONSTEXPR auto to_unsigned(Int value) -> + typename std::make_unsigned<Int>::type { FMT_ASSERT(value >= 0, "negative value"); return static_cast<typename std::make_unsigned<Int>::type>(value); } -FMT_SUPPRESS_MSC_WARNING(4566) constexpr unsigned char micro[] = "\u00B5"; +FMT_MSC_WARNING(suppress : 4566) constexpr unsigned char micro[] = "\u00B5"; -template <typename Char> constexpr bool is_unicode() { - return FMT_UNICODE || sizeof(Char) != 1 || - (sizeof(micro) == 3 && micro[0] == 0xC2 && micro[1] == 0xB5); +constexpr auto is_utf8() -> bool { + // Avoid buggy sign extensions in MSVC's constant evaluation mode (#2297). + using uchar = unsigned char; + return FMT_UNICODE || (sizeof(micro) == 3 && uchar(micro[0]) == 0xC2 && + uchar(micro[1]) == 0xB5); } - -#ifdef __cpp_char8_t -using char8_type = char8_t; -#else -enum char8_type : unsigned char {}; -#endif -} // namespace detail - -#ifdef FMT_USE_INTERNAL -namespace internal = detail; // DEPRECATED -#endif +FMT_END_DETAIL_NAMESPACE /** An implementation of ``std::basic_string_view`` for pre-C++17. It provides a @@ -361,12 +429,11 @@ template <typename Char> class basic_string_view { using value_type = Char; using iterator = const Char*; - constexpr basic_string_view() FMT_NOEXCEPT : data_(nullptr), size_(0) {} + constexpr basic_string_view() noexcept : data_(nullptr), size_(0) {} /** Constructs a string reference object from a C string and a size. */ - constexpr basic_string_view(const Char* s, size_t count) FMT_NOEXCEPT - : data_(s), - size_(count) {} + constexpr basic_string_view(const Char* s, size_t count) noexcept + : data_(s), size_(count) {} /** \rst @@ -374,42 +441,46 @@ template <typename Char> class basic_string_view { the size with ``std::char_traits<Char>::length``. \endrst */ -#if __cplusplus >= 201703L // C++17's char_traits::length() is constexpr. - FMT_CONSTEXPR -#endif + FMT_CONSTEXPR_CHAR_TRAITS + FMT_INLINE basic_string_view(const Char* s) - : data_(s), size_(std::char_traits<Char>::length(s)) {} + : data_(s), + size_(detail::const_check(std::is_same<Char, char>::value && + !detail::is_constant_evaluated(true)) + ? std::strlen(reinterpret_cast<const char*>(s)) + : std::char_traits<Char>::length(s)) {} /** Constructs a string reference from a ``std::basic_string`` object. */ template <typename Traits, typename Alloc> FMT_CONSTEXPR basic_string_view( - const std::basic_string<Char, Traits, Alloc>& s) FMT_NOEXCEPT - : data_(s.data()), - size_(s.size()) {} + const std::basic_string<Char, Traits, Alloc>& s) noexcept + : data_(s.data()), size_(s.size()) {} template <typename S, FMT_ENABLE_IF(std::is_same< S, detail::std_string_view<Char>>::value)> - FMT_CONSTEXPR basic_string_view(S s) FMT_NOEXCEPT : data_(s.data()), - size_(s.size()) {} + FMT_CONSTEXPR basic_string_view(S s) noexcept + : data_(s.data()), size_(s.size()) {} /** Returns a pointer to the string data. */ - constexpr const Char* data() const { return data_; } + constexpr auto data() const noexcept -> const Char* { return data_; } /** Returns the string size. */ - constexpr size_t size() const { return size_; } + constexpr auto size() const noexcept -> size_t { return size_; } - constexpr iterator begin() const { return data_; } - constexpr iterator end() const { return data_ + size_; } + constexpr auto begin() const noexcept -> iterator { return data_; } + constexpr auto end() const noexcept -> iterator { return data_ + size_; } - constexpr const Char& operator[](size_t pos) const { return data_[pos]; } + constexpr auto operator[](size_t pos) const noexcept -> const Char& { + return data_[pos]; + } - FMT_CONSTEXPR void remove_prefix(size_t n) { + FMT_CONSTEXPR void remove_prefix(size_t n) noexcept { data_ += n; size_ -= n; } // Lexicographically compare this string reference to other. - int compare(basic_string_view other) const { + FMT_CONSTEXPR_CHAR_TRAITS auto compare(basic_string_view other) const -> int { size_t str_size = size_ < other.size_ ? size_ : other.size_; int result = std::char_traits<Char>::compare(data_, other.data_, str_size); if (result == 0) @@ -417,94 +488,73 @@ template <typename Char> class basic_string_view { return result; } - friend bool operator==(basic_string_view lhs, basic_string_view rhs) { + FMT_CONSTEXPR_CHAR_TRAITS friend auto operator==(basic_string_view lhs, + basic_string_view rhs) + -> bool { return lhs.compare(rhs) == 0; } - friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { + friend auto operator!=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) != 0; } - friend bool operator<(basic_string_view lhs, basic_string_view rhs) { + friend auto operator<(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) < 0; } - friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { + friend auto operator<=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) <= 0; } - friend bool operator>(basic_string_view lhs, basic_string_view rhs) { + friend auto operator>(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) > 0; } - friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { + friend auto operator>=(basic_string_view lhs, basic_string_view rhs) -> bool { return lhs.compare(rhs) >= 0; } }; using string_view = basic_string_view<char>; -using wstring_view = basic_string_view<wchar_t>; /** Specifies if ``T`` is a character type. Can be specialized by users. */ template <typename T> struct is_char : std::false_type {}; template <> struct is_char<char> : std::true_type {}; -template <> struct is_char<wchar_t> : std::true_type {}; -template <> struct is_char<detail::char8_type> : std::true_type {}; -template <> struct is_char<char16_t> : std::true_type {}; -template <> struct is_char<char32_t> : std::true_type {}; -/** - \rst - Returns a string view of `s`. In order to add custom string type support to - {fmt} provide an overload of `to_string_view` for it in the same namespace as - the type for the argument-dependent lookup to work. +FMT_BEGIN_DETAIL_NAMESPACE - **Example**:: +// A base class for compile-time strings. +struct compile_string {}; - namespace my_ns { - inline string_view to_string_view(const my_string& s) { - return {s.data(), s.length()}; - } - } - std::string message = fmt::format(my_string("The answer is {}"), 42); - \endrst - */ +template <typename S> +struct is_compile_string : std::is_base_of<compile_string, S> {}; + +// Returns a string view of `s`. template <typename Char, FMT_ENABLE_IF(is_char<Char>::value)> -inline basic_string_view<Char> to_string_view(const Char* s) { +FMT_INLINE auto to_string_view(const Char* s) -> basic_string_view<Char> { return s; } - template <typename Char, typename Traits, typename Alloc> -inline basic_string_view<Char> to_string_view( - const std::basic_string<Char, Traits, Alloc>& s) { +inline auto to_string_view(const std::basic_string<Char, Traits, Alloc>& s) + -> basic_string_view<Char> { return s; } - template <typename Char> -inline basic_string_view<Char> to_string_view(basic_string_view<Char> s) { +constexpr auto to_string_view(basic_string_view<Char> s) + -> basic_string_view<Char> { return s; } - template <typename Char, - FMT_ENABLE_IF(!std::is_empty<detail::std_string_view<Char>>::value)> -inline basic_string_view<Char> to_string_view(detail::std_string_view<Char> s) { + FMT_ENABLE_IF(!std::is_empty<std_string_view<Char>>::value)> +inline auto to_string_view(std_string_view<Char> s) -> basic_string_view<Char> { return s; } - -// A base class for compile-time strings. It is defined in the fmt namespace to -// make formatting functions visible via ADL, e.g. format(FMT_STRING("{}"), 42). -struct compile_string {}; - -template <typename S> -struct is_compile_string : std::is_base_of<compile_string, S> {}; - template <typename S, FMT_ENABLE_IF(is_compile_string<S>::value)> -constexpr basic_string_view<typename S::char_type> to_string_view(const S& s) { - return s; +constexpr auto to_string_view(const S& s) + -> basic_string_view<typename S::char_type> { + return basic_string_view<typename S::char_type>(s); } - -namespace detail { void to_string_view(...); -using fmt::v7::to_string_view; // Specifies whether S is a string type convertible to fmt::basic_string_view. // It should be a constexpr function but MSVC 2017 fails to compile it in // enable_if and MSVC 2015 fails to compile it as an alias template. +// ADL invocation of to_string_view is DEPRECATED! template <typename S> struct is_string : std::is_class<decltype(to_string_view(std::declval<S>()))> { }; @@ -515,26 +565,73 @@ template <typename S> struct char_t_impl<S, enable_if_t<is_string<S>::value>> { using type = typename result::value_type; }; -// Reports a compile-time error if S is not a valid format string. -template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)> -FMT_INLINE void check_format_string(const S&) { -#ifdef FMT_ENFORCE_COMPILE_STRING - static_assert(is_compile_string<S>::value, - "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " - "FMT_STRING."); -#endif +enum class type { + none_type, + // Integer types should go first, + int_type, + uint_type, + long_long_type, + ulong_long_type, + int128_type, + uint128_type, + bool_type, + char_type, + last_integer_type = char_type, + // followed by floating-point types. + float_type, + double_type, + long_double_type, + last_numeric_type = long_double_type, + cstring_type, + string_type, + pointer_type, + custom_type +}; + +// Maps core type T to the corresponding type enum constant. +template <typename T, typename Char> +struct type_constant : std::integral_constant<type, type::custom_type> {}; + +#define FMT_TYPE_CONSTANT(Type, constant) \ + template <typename Char> \ + struct type_constant<Type, Char> \ + : std::integral_constant<type, type::constant> {} + +FMT_TYPE_CONSTANT(int, int_type); +FMT_TYPE_CONSTANT(unsigned, uint_type); +FMT_TYPE_CONSTANT(long long, long_long_type); +FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); +FMT_TYPE_CONSTANT(int128_opt, int128_type); +FMT_TYPE_CONSTANT(uint128_opt, uint128_type); +FMT_TYPE_CONSTANT(bool, bool_type); +FMT_TYPE_CONSTANT(Char, char_type); +FMT_TYPE_CONSTANT(float, float_type); +FMT_TYPE_CONSTANT(double, double_type); +FMT_TYPE_CONSTANT(long double, long_double_type); +FMT_TYPE_CONSTANT(const Char*, cstring_type); +FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type); +FMT_TYPE_CONSTANT(const void*, pointer_type); + +constexpr bool is_integral_type(type t) { + return t > type::none_type && t <= type::last_integer_type; } -template <typename..., typename S, FMT_ENABLE_IF(is_compile_string<S>::value)> -void check_format_string(S); + +constexpr bool is_arithmetic_type(type t) { + return t > type::none_type && t <= type::last_numeric_type; +} + +FMT_NORETURN FMT_API void throw_format_error(const char* message); struct error_handler { constexpr error_handler() = default; constexpr error_handler(const error_handler&) = default; // This function is intentionally not constexpr to give a compile-time error. - FMT_NORETURN FMT_API void on_error(const char* message); + FMT_NORETURN void on_error(const char* message) { + throw_format_error(message); + } }; -} // namespace detail +FMT_END_DETAIL_NAMESPACE /** String's character type. */ template <typename S> using char_t = typename detail::char_t_impl<S>::type; @@ -543,16 +640,7 @@ template <typename S> using char_t = typename detail::char_t_impl<S>::type; \rst Parsing context consisting of a format string range being parsed and an argument counter for automatic indexing. - - You can use one of the following type aliases for common character types: - - +-----------------------+-------------------------------------+ - | Type | Definition | - +=======================+=====================================+ - | format_parse_context | basic_format_parse_context<char> | - +-----------------------+-------------------------------------+ - | wformat_parse_context | basic_format_parse_context<wchar_t> | - +-----------------------+-------------------------------------+ + You can use the ``format_parse_context`` type alias for ``char`` instead. \endrst */ template <typename Char, typename ErrorHandler = detail::error_handler> @@ -561,6 +649,8 @@ class basic_format_parse_context : private ErrorHandler { basic_string_view<Char> format_str_; int next_arg_id_; + FMT_CONSTEXPR void do_check_arg_id(int id); + public: using char_type = Char; using iterator = typename basic_string_view<Char>::iterator; @@ -574,12 +664,14 @@ class basic_format_parse_context : private ErrorHandler { Returns an iterator to the beginning of the format string range being parsed. */ - constexpr iterator begin() const FMT_NOEXCEPT { return format_str_.begin(); } + constexpr auto begin() const noexcept -> iterator { + return format_str_.begin(); + } /** Returns an iterator past the end of the format string range being parsed. */ - constexpr iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + constexpr auto end() const noexcept -> iterator { return format_str_.end(); } /** Advances the begin iterator to ``it``. */ FMT_CONSTEXPR void advance_to(iterator it) { @@ -590,23 +682,27 @@ class basic_format_parse_context : private ErrorHandler { Reports an error if using the manual argument indexing; otherwise returns 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; + FMT_CONSTEXPR auto next_arg_id() -> int { + if (next_arg_id_ < 0) { + on_error("cannot switch from manual to automatic argument indexing"); + return 0; + } + int id = next_arg_id_++; + do_check_arg_id(id); + return id; } /** Reports an error if using the automatic argument indexing; otherwise switches to the manual indexing. */ - FMT_CONSTEXPR void check_arg_id(int) { - if (next_arg_id_ > 0) + FMT_CONSTEXPR void check_arg_id(int id) { + if (next_arg_id_ > 0) { on_error("cannot switch from automatic to manual argument indexing"); - else - next_arg_id_ = -1; + return; + } + next_arg_id_ = -1; + do_check_arg_id(id); } FMT_CONSTEXPR void check_arg_id(basic_string_view<Char>) {} @@ -615,11 +711,54 @@ class basic_format_parse_context : private ErrorHandler { ErrorHandler::on_error(message); } - constexpr ErrorHandler error_handler() const { return *this; } + constexpr auto error_handler() const -> ErrorHandler { return *this; } }; using format_parse_context = basic_format_parse_context<char>; -using wformat_parse_context = basic_format_parse_context<wchar_t>; + +FMT_BEGIN_DETAIL_NAMESPACE +// A parse context with extra data used only in compile-time checks. +template <typename Char, typename ErrorHandler = detail::error_handler> +class compile_parse_context + : public basic_format_parse_context<Char, ErrorHandler> { + private: + int num_args_; + const type* types_; + using base = basic_format_parse_context<Char, ErrorHandler>; + + public: + explicit FMT_CONSTEXPR compile_parse_context( + basic_string_view<Char> format_str, int num_args, const type* types, + ErrorHandler eh = {}, int next_arg_id = 0) + : base(format_str, eh, next_arg_id), num_args_(num_args), types_(types) {} + + constexpr int num_args() const { return num_args_; } + + FMT_CONSTEXPR auto next_arg_id() -> int { + int id = base::next_arg_id(); + if (id >= num_args_) this->on_error("argument not found"); + return id; + } + + FMT_CONSTEXPR void check_arg_id(int id) { + base::check_arg_id(id); + if (id >= num_args_) this->on_error("argument not found"); + } + using base::check_arg_id; +}; +FMT_END_DETAIL_NAMESPACE + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void +basic_format_parse_context<Char, ErrorHandler>::do_check_arg_id(int id) { + // Argument id is only checked at compile-time during parsing because + // formatting has its own validation. + if (detail::is_constant_evaluated() && FMT_GCC_VERSION >= 1200) { + using context = detail::compile_parse_context<Char, ErrorHandler>; + if (id >= static_cast<context*>(this)->num_args()) + on_error("argument not found"); + } +} template <typename Context> class basic_format_arg; template <typename Context> class basic_format_args; @@ -643,19 +782,55 @@ template <typename T> struct is_contiguous : std::false_type {}; template <typename Char> struct is_contiguous<std::basic_string<Char>> : std::true_type {}; -namespace detail { +class appender; + +FMT_BEGIN_DETAIL_NAMESPACE + +template <typename Context, typename T> +constexpr auto has_const_formatter_impl(T*) + -> decltype(typename Context::template formatter_type<T>().format( + std::declval<const T&>(), std::declval<Context&>()), + true) { + return true; +} +template <typename Context> +constexpr auto has_const_formatter_impl(...) -> bool { + return false; +} +template <typename T, typename Context> +constexpr auto has_const_formatter() -> bool { + return has_const_formatter_impl<Context>(static_cast<T*>(nullptr)); +} // Extracts a reference to the container from back_insert_iterator. template <typename Container> -inline Container& get_container(std::back_insert_iterator<Container> it) { - using bi_iterator = std::back_insert_iterator<Container>; - struct accessor : bi_iterator { - accessor(bi_iterator iter) : bi_iterator(iter) {} - using bi_iterator::container; +inline auto get_container(std::back_insert_iterator<Container> it) + -> Container& { + using base = std::back_insert_iterator<Container>; + struct accessor : base { + accessor(base b) : base(b) {} + using base::container; }; return *accessor(it).container; } +template <typename Char, typename InputIt, typename OutputIt> +FMT_CONSTEXPR auto copy_str(InputIt begin, InputIt end, OutputIt out) + -> OutputIt { + while (begin != end) *out++ = static_cast<Char>(*begin++); + return out; +} + +template <typename Char, typename T, typename U, + FMT_ENABLE_IF( + std::is_same<remove_const_t<T>, U>::value&& is_char<U>::value)> +FMT_CONSTEXPR auto copy_str(T* begin, T* end, U* out) -> U* { + if (is_constant_evaluated()) return copy_str<Char, T*, U*>(begin, end, out); + auto size = to_unsigned(end - begin); + memcpy(out, begin, size * sizeof(U)); + return out + size; +} + /** \rst A contiguous memory buffer with an optional growing ability. It is an internal @@ -670,24 +845,23 @@ template <typename T> class buffer { protected: // Don't initialize ptr_ since it is not accessed to save a few cycles. - FMT_SUPPRESS_MSC_WARNING(26495) - buffer(size_t sz) FMT_NOEXCEPT : size_(sz), capacity_(sz) {} + FMT_MSC_WARNING(suppress : 26495) + buffer(size_t sz) noexcept : size_(sz), capacity_(sz) {} - buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) FMT_NOEXCEPT - : ptr_(p), - size_(sz), - capacity_(cap) {} + FMT_CONSTEXPR20 buffer(T* p = nullptr, size_t sz = 0, size_t cap = 0) noexcept + : ptr_(p), size_(sz), capacity_(cap) {} - ~buffer() = default; + FMT_CONSTEXPR20 ~buffer() = default; + buffer(buffer&&) = default; /** Sets the buffer data and capacity. */ - void set(T* buf_data, size_t buf_capacity) FMT_NOEXCEPT { + FMT_CONSTEXPR void set(T* buf_data, size_t buf_capacity) noexcept { ptr_ = buf_data; capacity_ = buf_capacity; } /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(size_t capacity) = 0; + virtual FMT_CONSTEXPR20 void grow(size_t capacity) = 0; public: using value_type = T; @@ -696,30 +870,30 @@ template <typename T> class buffer { buffer(const buffer&) = delete; void operator=(const buffer&) = delete; - T* begin() FMT_NOEXCEPT { return ptr_; } - T* end() FMT_NOEXCEPT { return ptr_ + size_; } + auto begin() noexcept -> T* { return ptr_; } + auto end() noexcept -> T* { return ptr_ + size_; } - const T* begin() const FMT_NOEXCEPT { return ptr_; } - const T* end() const FMT_NOEXCEPT { return ptr_ + size_; } + auto begin() const noexcept -> const T* { return ptr_; } + auto end() const noexcept -> const T* { return ptr_ + size_; } /** Returns the size of this buffer. */ - size_t size() const FMT_NOEXCEPT { return size_; } + constexpr auto size() const noexcept -> size_t { return size_; } /** Returns the capacity of this buffer. */ - size_t capacity() const FMT_NOEXCEPT { return capacity_; } + constexpr auto capacity() const noexcept -> size_t { return capacity_; } /** Returns a pointer to the buffer data. */ - T* data() FMT_NOEXCEPT { return ptr_; } + FMT_CONSTEXPR auto data() noexcept -> T* { return ptr_; } /** Returns a pointer to the buffer data. */ - const T* data() const FMT_NOEXCEPT { return ptr_; } + FMT_CONSTEXPR auto data() const noexcept -> const T* { return ptr_; } /** Clears this buffer. */ void clear() { size_ = 0; } // Tries resizing the buffer to contain *count* elements. If T is a POD type // the new elements may not be initialized. - void try_resize(size_t count) { + FMT_CONSTEXPR20 void try_resize(size_t count) { try_reserve(count); size_ = count <= capacity_ ? count : capacity_; } @@ -728,11 +902,11 @@ template <typename T> class buffer { // capacity by a smaller amount than requested but guarantees there is space // for at least one additional element either by increasing the capacity or by // flushing the buffer if it is full. - void try_reserve(size_t new_capacity) { + FMT_CONSTEXPR20 void try_reserve(size_t new_capacity) { if (new_capacity > capacity_) grow(new_capacity); } - void push_back(const T& value) { + FMT_CONSTEXPR20 void push_back(const T& value) { try_reserve(size_ + 1); ptr_[size_++] = value; } @@ -740,16 +914,19 @@ template <typename T> class buffer { /** Appends data to the end of the buffer. */ template <typename U> void append(const U* begin, const U* end); - template <typename I> T& operator[](I index) { return ptr_[index]; } - template <typename I> const T& operator[](I index) const { + template <typename I> FMT_CONSTEXPR auto operator[](I index) -> T& { + return ptr_[index]; + } + template <typename I> + FMT_CONSTEXPR auto operator[](I index) const -> const T& { return ptr_[index]; } }; struct buffer_traits { explicit buffer_traits(size_t) {} - size_t count() const { return 0; } - size_t limit(size_t size) { return size; } + auto count() const -> size_t { return 0; } + auto limit(size_t size) -> size_t { return size; } }; class fixed_buffer_traits { @@ -759,8 +936,8 @@ class fixed_buffer_traits { public: explicit fixed_buffer_traits(size_t limit) : limit_(limit) {} - size_t count() const { return count_; } - size_t limit(size_t size) { + auto count() const -> size_t { return count_; } + auto limit(size_t size) -> size_t { size_t n = limit_ > count_ ? limit_ - count_ : 0; count_ += size; return size < n ? size : n; @@ -776,33 +953,84 @@ class iterator_buffer final : public Traits, public buffer<T> { T data_[buffer_size]; protected: - void grow(size_t) final FMT_OVERRIDE { + FMT_CONSTEXPR20 void grow(size_t) override { if (this->size() == buffer_size) flush(); } - void flush(); + + void flush() { + auto size = this->size(); + this->clear(); + out_ = copy_str<T>(data_, data_ + this->limit(size), out_); + } public: explicit iterator_buffer(OutputIt out, size_t n = buffer_size) - : Traits(n), - buffer<T>(data_, 0, buffer_size), - out_(out) {} + : Traits(n), buffer<T>(data_, 0, buffer_size), out_(out) {} + iterator_buffer(iterator_buffer&& other) + : Traits(other), buffer<T>(data_, 0, buffer_size), out_(other.out_) {} + ~iterator_buffer() { flush(); } + + auto out() -> OutputIt { + flush(); + return out_; + } + auto count() const -> size_t { return Traits::count() + this->size(); } +}; + +template <typename T> +class iterator_buffer<T*, T, fixed_buffer_traits> final + : public fixed_buffer_traits, + public buffer<T> { + private: + T* out_; + enum { buffer_size = 256 }; + T data_[buffer_size]; + + protected: + FMT_CONSTEXPR20 void grow(size_t) override { + if (this->size() == this->capacity()) flush(); + } + + void flush() { + size_t n = this->limit(this->size()); + if (this->data() == out_) { + out_ += n; + this->set(data_, buffer_size); + } + this->clear(); + } + + public: + explicit iterator_buffer(T* out, size_t n = buffer_size) + : fixed_buffer_traits(n), buffer<T>(out, 0, n), out_(out) {} + iterator_buffer(iterator_buffer&& other) + : fixed_buffer_traits(other), + buffer<T>(std::move(other)), + out_(other.out_) { + if (this->data() != out_) { + this->set(data_, buffer_size); + this->clear(); + } + } ~iterator_buffer() { flush(); } - OutputIt out() { + auto out() -> T* { flush(); return out_; } - size_t count() const { return Traits::count() + this->size(); } + auto count() const -> size_t { + return fixed_buffer_traits::count() + this->size(); + } }; template <typename T> class iterator_buffer<T*, T> final : public buffer<T> { protected: - void grow(size_t) final FMT_OVERRIDE {} + FMT_CONSTEXPR20 void grow(size_t) override {} public: explicit iterator_buffer(T* out, size_t = 0) : buffer<T>(out, 0, ~size_t()) {} - T* out() { return &*this->end(); } + auto out() -> T* { return &*this->end(); } }; // A buffer that writes to a container with the contiguous storage. @@ -815,7 +1043,7 @@ class iterator_buffer<std::back_insert_iterator<Container>, Container& container_; protected: - void grow(size_t capacity) final FMT_OVERRIDE { + FMT_CONSTEXPR20 void grow(size_t capacity) override { container_.resize(capacity); this->set(&container_[0], capacity); } @@ -825,7 +1053,8 @@ class iterator_buffer<std::back_insert_iterator<Container>, : buffer<typename Container::value_type>(c.size()), container_(c) {} explicit iterator_buffer(std::back_insert_iterator<Container> out, size_t = 0) : iterator_buffer(get_container(out)) {} - std::back_insert_iterator<Container> out() { + + auto out() -> std::back_insert_iterator<Container> { return std::back_inserter(container_); } }; @@ -838,7 +1067,7 @@ template <typename T = char> class counting_buffer final : public buffer<T> { size_t count_ = 0; protected: - void grow(size_t) final FMT_OVERRIDE { + FMT_CONSTEXPR20 void grow(size_t) override { if (this->size() != buffer_size) return; count_ += this->size(); this->clear(); @@ -847,48 +1076,24 @@ template <typename T = char> class counting_buffer final : public buffer<T> { public: counting_buffer() : buffer<T>(data_, 0, buffer_size) {} - size_t count() { return count_ + this->size(); } + auto count() -> size_t { return count_ + this->size(); } }; -// An output iterator that appends to the buffer. -// It is used to reduce symbol sizes for the common case. template <typename T> -class buffer_appender : public std::back_insert_iterator<buffer<T>> { - using base = std::back_insert_iterator<buffer<T>>; - - public: - explicit buffer_appender(buffer<T>& buf) : base(buf) {} - buffer_appender(base it) : base(it) {} - - buffer_appender& operator++() { - base::operator++(); - return *this; - } - - buffer_appender operator++(int) { - buffer_appender tmp = *this; - ++*this; - return tmp; - } -}; +using buffer_appender = conditional_t<std::is_same<T, char>::value, appender, + std::back_insert_iterator<buffer<T>>>; -// Maps an output iterator into a buffer. +// Maps an output iterator to a buffer. template <typename T, typename OutputIt> -iterator_buffer<OutputIt, T> get_buffer(OutputIt); -template <typename T> buffer<T>& get_buffer(buffer_appender<T>); - -template <typename OutputIt> OutputIt get_buffer_init(OutputIt out) { - return out; -} -template <typename T> buffer<T>& get_buffer_init(buffer_appender<T> out) { - return get_container(out); +auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> { + return iterator_buffer<OutputIt, T>(out); } template <typename Buffer> auto get_iterator(Buffer& buf) -> decltype(buf.out()) { return buf.out(); } -template <typename T> buffer_appender<T> get_iterator(buffer<T>& buf) { +template <typename T> auto get_iterator(buffer<T>& buf) -> buffer_appender<T> { return buffer_appender<T>(buf); } @@ -898,9 +1103,13 @@ struct fallback_formatter { }; // Specifies if T has an enabled fallback_formatter specialization. -template <typename T, typename Context> +template <typename T, typename Char> using has_fallback_formatter = - std::is_constructible<fallback_formatter<T, typename Context::char_type>>; +#ifdef FMT_DEPRECATED_OSTREAM + std::is_constructible<fallback_formatter<T, Char>>; +#else + std::false_type; +#endif struct view {}; @@ -925,8 +1134,8 @@ struct arg_data { template <typename... U> 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<Char>* named_args() { return named_args_; } + auto args() const -> const T* { return args_ + 1; } + auto named_args() -> named_arg_info<Char>* { return named_args_; } }; template <typename T, typename Char, size_t NUM_ARGS> @@ -935,99 +1144,59 @@ struct arg_data<T, Char, NUM_ARGS, 0> { T args_[NUM_ARGS != 0 ? NUM_ARGS : +1]; template <typename... U> - 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; } + FMT_CONSTEXPR FMT_INLINE arg_data(const U&... init) : args_{init...} {} + FMT_CONSTEXPR FMT_INLINE auto args() const -> const T* { return args_; } + FMT_CONSTEXPR FMT_INLINE auto named_args() -> std::nullptr_t { + return nullptr; + } }; template <typename Char> inline void init_named_args(named_arg_info<Char>*, int, int) {} -template <typename Char, typename T, typename... Tail> +template <typename T> struct is_named_arg : std::false_type {}; +template <typename T> struct is_statically_named_arg : std::false_type {}; + +template <typename T, typename Char> +struct is_named_arg<named_arg<Char, T>> : std::true_type {}; + +template <typename Char, typename T, typename... Tail, + FMT_ENABLE_IF(!is_named_arg<T>::value)> void init_named_args(named_arg_info<Char>* 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 <typename Char, typename T, typename... Tail> +template <typename Char, typename T, typename... Tail, + FMT_ENABLE_IF(is_named_arg<T>::value)> void init_named_args(named_arg_info<Char>* named_args, int arg_count, - int named_arg_count, const named_arg<Char, T>& arg, - const Tail&... args) { + int named_arg_count, const T& 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 <typename... Args> -FMT_INLINE void init_named_args(std::nullptr_t, int, int, const Args&...) {} - -template <typename T> struct is_named_arg : std::false_type {}; - -template <typename T, typename Char> -struct is_named_arg<named_arg<Char, T>> : std::true_type {}; +FMT_CONSTEXPR FMT_INLINE void init_named_args(std::nullptr_t, int, int, + const Args&...) {} -template <bool B = false> constexpr size_t count() { return B ? 1 : 0; } -template <bool B1, bool B2, bool... Tail> constexpr size_t count() { +template <bool B = false> constexpr auto count() -> size_t { return B ? 1 : 0; } +template <bool B1, bool B2, bool... Tail> constexpr auto count() -> size_t { return (B1 ? 1 : 0) + count<B2, Tail...>(); } -template <typename... Args> constexpr size_t count_named_args() { +template <typename... Args> constexpr auto count_named_args() -> size_t { return count<is_named_arg<Args>::value...>(); } -enum class type { - none_type, - // Integer types should go first, - int_type, - uint_type, - long_long_type, - ulong_long_type, - int128_type, - uint128_type, - bool_type, - char_type, - last_integer_type = char_type, - // followed by floating-point types. - float_type, - double_type, - long_double_type, - last_numeric_type = long_double_type, - cstring_type, - string_type, - pointer_type, - custom_type -}; - -// Maps core type T to the corresponding type enum constant. -template <typename T, typename Char> -struct type_constant : std::integral_constant<type, type::custom_type> {}; - -#define FMT_TYPE_CONSTANT(Type, constant) \ - template <typename Char> \ - struct type_constant<Type, Char> \ - : std::integral_constant<type, type::constant> {} - -FMT_TYPE_CONSTANT(int, int_type); -FMT_TYPE_CONSTANT(unsigned, uint_type); -FMT_TYPE_CONSTANT(long long, long_long_type); -FMT_TYPE_CONSTANT(unsigned long long, ulong_long_type); -FMT_TYPE_CONSTANT(int128_t, int128_type); -FMT_TYPE_CONSTANT(uint128_t, uint128_type); -FMT_TYPE_CONSTANT(bool, bool_type); -FMT_TYPE_CONSTANT(Char, char_type); -FMT_TYPE_CONSTANT(float, float_type); -FMT_TYPE_CONSTANT(double, double_type); -FMT_TYPE_CONSTANT(long double, long_double_type); -FMT_TYPE_CONSTANT(const Char*, cstring_type); -FMT_TYPE_CONSTANT(basic_string_view<Char>, string_type); -FMT_TYPE_CONSTANT(const void*, pointer_type); - -constexpr bool is_integral_type(type t) { - return t > type::none_type && t <= type::last_integer_type; +template <typename... Args> +constexpr auto count_statically_named_args() -> size_t { + return count<is_statically_named_arg<Args>::value...>(); } -constexpr bool is_arithmetic_type(type t) { - return t > type::none_type && t <= type::last_numeric_type; -} +struct unformattable {}; +struct unformattable_char : unformattable {}; +struct unformattable_const : unformattable {}; +struct unformattable_pointer : unformattable {}; template <typename Char> struct string_value { const Char* data; @@ -1041,8 +1210,8 @@ template <typename Char> struct named_arg_value { template <typename Context> struct custom_value { using parse_context = typename Context::parse_context_type; - const void* value; - void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); + void* value; + void (*format)(void* arg, parse_context& parse_ctx, Context& ctx); }; // A formatting argument value. @@ -1051,12 +1220,13 @@ template <typename Context> class value { using char_type = typename Context::char_type; union { + monostate no_value; int int_value; unsigned uint_value; long long long_long_value; unsigned long long ulong_long_value; - int128_t int128_value; - uint128_t uint128_value; + int128_opt int128_value; + uint128_opt uint128_value; bool bool_value; char_type char_value; float float_value; @@ -1068,19 +1238,23 @@ template <typename Context> class value { named_arg_value<char_type> named_args; }; - constexpr FMT_INLINE value(int val = 0) : int_value(val) {} + constexpr FMT_INLINE value() : no_value() {} + constexpr FMT_INLINE value(int val) : 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) {} + constexpr FMT_INLINE value(long long val) : long_long_value(val) {} + constexpr FMT_INLINE value(unsigned long long val) : ulong_long_value(val) {} + FMT_INLINE value(int128_opt val) : int128_value(val) {} + FMT_INLINE value(uint128_opt val) : uint128_value(val) {} + constexpr FMT_INLINE value(float val) : float_value(val) {} + constexpr FMT_INLINE value(double val) : double_value(val) {} FMT_INLINE value(long double val) : long_double_value(val) {} - 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<char_type> val) { + constexpr FMT_INLINE value(bool val) : bool_value(val) {} + constexpr FMT_INLINE value(char_type val) : char_value(val) {} + FMT_CONSTEXPR FMT_INLINE value(const char_type* val) { + string.data = val; + if (is_constant_evaluated()) string.size = {}; + } + FMT_CONSTEXPR FMT_INLINE value(basic_string_view<char_type> val) { string.data = val.data(); string.size = val.size(); } @@ -1088,31 +1262,39 @@ template <typename Context> class value { FMT_INLINE value(const named_arg_info<char_type>* args, size_t size) : named_args{args, size} {} - template <typename T> FMT_INLINE value(const T& val) { - custom.value = &val; + template <typename T> FMT_CONSTEXPR FMT_INLINE value(T& val) { + using value_type = remove_cvref_t<T>; + custom.value = const_cast<value_type*>(&val); // Get the formatter type through the context to allow different contexts // have different extension points, e.g. `formatter<T>` for `format` and // `printf_formatter<T>` for `printf`. custom.format = format_custom_arg< - T, conditional_t<has_formatter<T, Context>::value, - typename Context::template formatter_type<T>, - fallback_formatter<T, char_type>>>; + value_type, + conditional_t<has_formatter<value_type, Context>::value, + typename Context::template formatter_type<value_type>, + fallback_formatter<value_type, char_type>>>; } + value(unformattable); + value(unformattable_char); + value(unformattable_const); + value(unformattable_pointer); private: // Formats an argument of a custom type, such as a user-defined class. template <typename T, typename Formatter> - static void format_custom_arg(const void* arg, + static void format_custom_arg(void* arg, typename Context::parse_context_type& parse_ctx, Context& ctx) { - Formatter f; + auto f = Formatter(); parse_ctx.advance_to(f.parse(parse_ctx)); - ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx)); + using qualified_type = + conditional_t<has_const_formatter<T, Context>(), const T, T>; + ctx.advance_to(f.format(*static_cast<qualified_type*>(arg), ctx)); } }; template <typename Context, typename T> -FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value); +FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg<Context>; // To minimize the number of types we need to deal with, long is translated // either to int or to long long depending on its size. @@ -1120,117 +1302,204 @@ enum { long_short = sizeof(long) == sizeof(int) }; using long_type = conditional_t<long_short, int, long long>; using ulong_type = conditional_t<long_short, unsigned, unsigned long long>; -struct unformattable {}; +#ifdef __cpp_lib_byte +inline auto format_as(std::byte b) -> unsigned char { + return static_cast<unsigned char>(b); +} +#endif + +template <typename T> struct has_format_as { + template <typename U, typename V = decltype(format_as(U())), + FMT_ENABLE_IF(std::is_enum<U>::value&& std::is_integral<V>::value)> + static auto check(U*) -> std::true_type; + static auto check(...) -> std::false_type; + + enum { value = decltype(check(static_cast<T*>(nullptr)))::value }; +}; // Maps formatting arguments to core types. +// arg_mapper reports errors by returning unformattable instead of using +// static_assert because it's used in the is_formattable trait. template <typename Context> struct arg_mapper { using char_type = typename Context::char_type; - FMT_CONSTEXPR int map(signed char val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned char val) { return val; } - FMT_CONSTEXPR int map(short val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned short val) { return val; } - FMT_CONSTEXPR int map(int val) { return val; } - FMT_CONSTEXPR unsigned map(unsigned val) { return val; } - FMT_CONSTEXPR long_type map(long val) { return val; } - FMT_CONSTEXPR ulong_type map(unsigned long val) { return val; } - FMT_CONSTEXPR long long map(long long val) { return val; } - FMT_CONSTEXPR unsigned long long map(unsigned long long val) { return val; } - FMT_CONSTEXPR int128_t map(int128_t val) { return val; } - FMT_CONSTEXPR uint128_t map(uint128_t val) { return val; } - FMT_CONSTEXPR bool map(bool val) { return val; } - - template <typename T, FMT_ENABLE_IF(is_char<T>::value)> - FMT_CONSTEXPR char_type map(T val) { - static_assert( - std::is_same<T, char>::value || std::is_same<T, char_type>::value, - "mixing character types is disallowed"); + FMT_CONSTEXPR FMT_INLINE auto map(signed char val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned char val) -> unsigned { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(short val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned short val) -> unsigned { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(int val) -> int { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned val) -> unsigned { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(long val) -> long_type { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned long val) -> ulong_type { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(long long val) -> long long { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(unsigned long long val) + -> unsigned long long { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(int128_opt val) -> int128_opt { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(uint128_opt val) -> uint128_opt { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(bool val) -> bool { return val; } - FMT_CONSTEXPR float map(float val) { return val; } - FMT_CONSTEXPR double map(double val) { return val; } - FMT_CONSTEXPR long double map(long double val) { return val; } + template <typename T, FMT_ENABLE_IF(std::is_same<T, char>::value || + std::is_same<T, char_type>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(T val) -> char_type { + return val; + } + template <typename T, enable_if_t<(std::is_same<T, wchar_t>::value || +#ifdef __cpp_char8_t + std::is_same<T, char8_t>::value || +#endif + std::is_same<T, char16_t>::value || + std::is_same<T, char32_t>::value) && + !std::is_same<T, char_type>::value, + int> = 0> + FMT_CONSTEXPR FMT_INLINE auto map(T) -> unformattable_char { + return {}; + } - FMT_CONSTEXPR const char_type* map(char_type* val) { return val; } - FMT_CONSTEXPR const char_type* map(const char_type* val) { return val; } - template <typename T, FMT_ENABLE_IF(is_string<T>::value)> - FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) { - static_assert(std::is_same<char_type, char_t<T>>::value, - "mixing character types is disallowed"); + FMT_CONSTEXPR FMT_INLINE auto map(float val) -> float { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(double val) -> double { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(long double val) -> long double { + return val; + } + + FMT_CONSTEXPR FMT_INLINE auto map(char_type* val) -> const char_type* { + return val; + } + FMT_CONSTEXPR FMT_INLINE auto map(const char_type* val) -> const char_type* { + return val; + } + template <typename T, + FMT_ENABLE_IF(is_string<T>::value && !std::is_pointer<T>::value && + std::is_same<char_type, char_t<T>>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view<char_type> { return to_string_view(val); } template <typename T, + FMT_ENABLE_IF(is_string<T>::value && !std::is_pointer<T>::value && + !std::is_same<char_type, char_t<T>>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T&) -> unformattable_char { + return {}; + } + template <typename T, FMT_ENABLE_IF( - std::is_constructible<basic_string_view<char_type>, T>::value && + std::is_convertible<T, basic_string_view<char_type>>::value && !is_string<T>::value && !has_formatter<T, Context>::value && - !has_fallback_formatter<T, Context>::value)> - FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) { + !has_fallback_formatter<T, char_type>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view<char_type> { return basic_string_view<char_type>(val); } - template < - typename T, - FMT_ENABLE_IF( - std::is_constructible<std_string_view<char_type>, T>::value && - !std::is_constructible<basic_string_view<char_type>, T>::value && - !is_string<T>::value && !has_formatter<T, Context>::value && - !has_fallback_formatter<T, Context>::value)> - FMT_CONSTEXPR basic_string_view<char_type> map(const T& val) { + template <typename T, + FMT_ENABLE_IF( + std::is_convertible<T, std_string_view<char_type>>::value && + !std::is_convertible<T, basic_string_view<char_type>>::value && + !is_string<T>::value && !has_formatter<T, Context>::value && + !has_fallback_formatter<T, char_type>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> basic_string_view<char_type> { return std_string_view<char_type>(val); } - FMT_CONSTEXPR const char* map(const signed char* val) { - static_assert(std::is_same<char_type, char>::value, "invalid string type"); - return reinterpret_cast<const char*>(val); - } - FMT_CONSTEXPR const char* map(const unsigned char* val) { - static_assert(std::is_same<char_type, char>::value, "invalid string type"); - return reinterpret_cast<const char*>(val); + + FMT_CONSTEXPR FMT_INLINE auto map(void* val) -> const void* { return val; } + FMT_CONSTEXPR FMT_INLINE auto map(const void* val) -> const void* { + return val; } - FMT_CONSTEXPR const char* map(signed char* val) { - const auto* const_val = val; - return map(const_val); + FMT_CONSTEXPR FMT_INLINE auto map(std::nullptr_t val) -> const void* { + return val; } - FMT_CONSTEXPR const char* map(unsigned char* val) { - const auto* const_val = val; - return map(const_val); + + // We use SFINAE instead of a const T* parameter to avoid conflicting with + // the C array overload. + template < + typename T, + FMT_ENABLE_IF( + std::is_pointer<T>::value || std::is_member_pointer<T>::value || + std::is_function<typename std::remove_pointer<T>::type>::value || + (std::is_convertible<const T&, const void*>::value && + !std::is_convertible<const T&, const char_type*>::value && + !has_formatter<T, Context>::value))> + FMT_CONSTEXPR auto map(const T&) -> unformattable_pointer { + return {}; } - FMT_CONSTEXPR const void* map(void* val) { return val; } - FMT_CONSTEXPR const void* map(const void* val) { return val; } - FMT_CONSTEXPR const void* map(std::nullptr_t val) { return val; } - template <typename T> FMT_CONSTEXPR int map(const T*) { - // Formatting of arbitrary pointers is disallowed. If you want to output - // a pointer cast it to "void *" or "const void *". In particular, this - // forbids formatting of "[const] volatile char *" which is printed as bool - // by iostreams. - static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); - return 0; + template <typename T, std::size_t N, + FMT_ENABLE_IF(!std::is_same<T, wchar_t>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T (&values)[N]) -> const T (&)[N] { + return values; } template <typename T, - FMT_ENABLE_IF(std::is_enum<T>::value && - !has_formatter<T, Context>::value && - !has_fallback_formatter<T, Context>::value)> - FMT_CONSTEXPR auto map(const T& val) + FMT_ENABLE_IF( + std::is_enum<T>::value&& std::is_convertible<T, int>::value && + !has_format_as<T>::value && !has_formatter<T, Context>::value && + !has_fallback_formatter<T, char_type>::value)> + FMT_DEPRECATED FMT_CONSTEXPR FMT_INLINE auto map(const T& val) -> decltype(std::declval<arg_mapper>().map( - static_cast<typename std::underlying_type<T>::type>(val))) { - return map(static_cast<typename std::underlying_type<T>::type>(val)); + static_cast<underlying_t<T>>(val))) { + return map(static_cast<underlying_t<T>>(val)); } - template <typename T, - FMT_ENABLE_IF(!is_string<T>::value && !is_char<T>::value && - (has_formatter<T, Context>::value || - has_fallback_formatter<T, Context>::value))> - FMT_CONSTEXPR const T& map(const T& val) { + + template <typename T, FMT_ENABLE_IF(has_format_as<T>::value && + !has_formatter<T, Context>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& val) + -> decltype(std::declval<arg_mapper>().map(format_as(T()))) { + return map(format_as(val)); + } + + template <typename T, typename U = remove_cvref_t<T>> + struct formattable + : bool_constant<has_const_formatter<U, Context>() || + !std::is_const<remove_reference_t<T>>::value || + has_fallback_formatter<U, char_type>::value> {}; + +#if (FMT_MSC_VERSION != 0 && FMT_MSC_VERSION < 1910) || \ + FMT_ICC_VERSION != 0 || defined(__NVCC__) + // Workaround a bug in MSVC and Intel (Issue 2746). + template <typename T> FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { + return val; + } +#else + template <typename T, FMT_ENABLE_IF(formattable<T>::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&& val) -> T& { return val; } + template <typename T, FMT_ENABLE_IF(!formattable<T>::value)> + FMT_CONSTEXPR FMT_INLINE auto do_map(T&&) -> unformattable_const { + return {}; + } +#endif - template <typename T> - FMT_CONSTEXPR auto map(const named_arg<char_type, T>& val) - -> decltype(std::declval<arg_mapper>().map(val.value)) { - return map(val.value); + template <typename T, typename U = remove_cvref_t<T>, + FMT_ENABLE_IF(!is_string<U>::value && !is_char<U>::value && + !std::is_array<U>::value && + !std::is_pointer<U>::value && + !has_format_as<U>::value && + (has_formatter<U, Context>::value || + has_fallback_formatter<U, char_type>::value))> + FMT_CONSTEXPR FMT_INLINE auto map(T&& val) + -> decltype(this->do_map(std::forward<T>(val))) { + return do_map(std::forward<T>(val)); } - unformattable map(...) { return {}; } + template <typename T, FMT_ENABLE_IF(is_named_arg<T>::value)> + FMT_CONSTEXPR FMT_INLINE auto map(const T& named_arg) + -> decltype(std::declval<arg_mapper>().map(named_arg.value)) { + return map(named_arg.value); + } + + auto map(...) -> unformattable { return {}; } }; // A type constant after applying arg_mapper<Context>. @@ -1244,7 +1513,27 @@ enum { packed_arg_bits = 4 }; enum { max_packed_args = 62 / packed_arg_bits }; enum : unsigned long long { is_unpacked_bit = 1ULL << 63 }; enum : unsigned long long { has_named_args_bit = 1ULL << 62 }; -} // namespace detail + +FMT_END_DETAIL_NAMESPACE + +// An output iterator that appends to a buffer. +// It is used to reduce symbol sizes for the common case. +class appender : public std::back_insert_iterator<detail::buffer<char>> { + using base = std::back_insert_iterator<detail::buffer<char>>; + + template <typename T> + friend auto get_buffer(appender out) -> detail::buffer<char>& { + return detail::get_container(out); + } + + public: + using std::back_insert_iterator<detail::buffer<char>>::back_insert_iterator; + appender(base it) noexcept : base(it) {} + FMT_UNCHECKED_ITERATOR(appender); + + auto operator++() noexcept -> appender& { return *this; } + auto operator++(int) noexcept -> appender { return *this; } +}; // A formatting argument. It is a trivially copyable/constructible type to // allow storage in basic_memory_buffer. @@ -1254,8 +1543,8 @@ template <typename Context> class basic_format_arg { detail::type type_; template <typename ContextType, typename T> - friend FMT_CONSTEXPR basic_format_arg<ContextType> detail::make_arg( - const T& value); + friend FMT_CONSTEXPR auto detail::make_arg(T&& value) + -> basic_format_arg<ContextType>; template <typename Visitor, typename Ctx> friend FMT_CONSTEXPR auto visit_format_arg(Visitor&& vis, @@ -1289,14 +1578,16 @@ template <typename Context> class basic_format_arg { constexpr basic_format_arg() : type_(detail::type::none_type) {} - constexpr explicit operator bool() const FMT_NOEXCEPT { + constexpr explicit operator bool() const noexcept { return type_ != detail::type::none_type; } - detail::type type() const { return type_; } + auto type() const -> detail::type { return type_; } - bool is_integral() const { return detail::is_integral_type(type_); } - bool is_arithmetic() const { return detail::is_arithmetic_type(type_); } + auto is_integral() const -> bool { return detail::is_integral_type(type_); } + auto is_arithmetic() const -> bool { + return detail::is_arithmetic_type(type_); + } }; /** @@ -1307,9 +1598,8 @@ template <typename Context> class basic_format_arg { \endrst */ template <typename Visitor, typename Context> -FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( +FMT_CONSTEXPR FMT_INLINE auto visit_format_arg( Visitor&& vis, const basic_format_arg<Context>& arg) -> decltype(vis(0)) { - using char_type = typename Context::char_type; switch (arg.type_) { case detail::type::none_type: break; @@ -1321,16 +1611,10 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( return vis(arg.value_.long_long_value); case detail::type::ulong_long_type: return vis(arg.value_.ulong_long_value); -#if FMT_USE_INT128 case detail::type::int128_type: - return vis(arg.value_.int128_value); + return vis(detail::convert_for_visit(arg.value_.int128_value)); case detail::type::uint128_type: - return vis(arg.value_.uint128_value); -#else - case detail::type::int128_type: - case detail::type::uint128_type: - break; -#endif + return vis(detail::convert_for_visit(arg.value_.uint128_value)); case detail::type::bool_type: return vis(arg.value_.bool_value); case detail::type::char_type: @@ -1344,8 +1628,8 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( case detail::type::cstring_type: return vis(arg.value_.string.data); case detail::type::string_type: - return vis(basic_string_view<char_type>(arg.value_.string.data, - arg.value_.string.size)); + using sv = basic_string_view<typename Context::char_type>; + return vis(sv(arg.value_.string.data, arg.value_.string.size)); case detail::type::pointer_type: return vis(arg.value_.pointer); case detail::type::custom_type: @@ -1354,14 +1638,22 @@ FMT_CONSTEXPR_DECL FMT_INLINE auto visit_format_arg( return vis(monostate()); } -template <typename T> struct formattable : std::false_type {}; +FMT_BEGIN_DETAIL_NAMESPACE -namespace detail { +template <typename Char, typename InputIt> +auto copy_str(InputIt begin, InputIt end, appender out) -> appender { + get_container(out).append(begin, end); + return out; +} +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 500 // A workaround for gcc 4.8 to make void_t work in a SFINAE context. template <typename... Ts> struct void_t_impl { using type = void; }; template <typename... Ts> using void_t = typename detail::void_t_impl<Ts...>::type; +#else +template <typename...> using void_t = void; +#endif template <typename It, typename T, typename Enable = void> struct is_output_iterator : std::false_type {}; @@ -1384,49 +1676,69 @@ struct is_contiguous_back_insert_iterator : std::false_type {}; template <typename Container> struct is_contiguous_back_insert_iterator<std::back_insert_iterator<Container>> : is_contiguous<Container> {}; -template <typename Char> -struct is_contiguous_back_insert_iterator<buffer_appender<Char>> - : std::true_type {}; +template <> +struct is_contiguous_back_insert_iterator<appender> : std::true_type {}; -// A type-erased reference to an std::locale to avoid heavy <locale> include. +// A type-erased reference to an std::locale to avoid a heavy <locale> include. class locale_ref { private: const void* locale_; // A type-erased pointer to std::locale. public: - locale_ref() : locale_(nullptr) {} + constexpr locale_ref() : locale_(nullptr) {} template <typename Locale> explicit locale_ref(const Locale& loc); - explicit operator bool() const FMT_NOEXCEPT { return locale_ != nullptr; } + explicit operator bool() const noexcept { return locale_ != nullptr; } - template <typename Locale> Locale get() const; + template <typename Locale> auto get() const -> Locale; }; -template <typename> constexpr unsigned long long encode_types() { return 0; } +template <typename> constexpr auto encode_types() -> unsigned long long { + return 0; +} template <typename Context, typename Arg, typename... Args> -constexpr unsigned long long encode_types() { +constexpr auto encode_types() -> unsigned long long { return static_cast<unsigned>(mapped_type_constant<Arg, Context>::value) | (encode_types<Context, Args...>() << packed_arg_bits); } template <typename Context, typename T> -FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T& value) { - basic_format_arg<Context> arg; - arg.type_ = mapped_type_constant<T, Context>::value; - arg.value_ = arg_mapper<Context>().map(value); - return arg; -} - -template <typename T> int check(unformattable) { +FMT_CONSTEXPR FMT_INLINE auto make_value(T&& val) -> value<Context> { + const auto& arg = arg_mapper<Context>().map(std::forward<T>(val)); + + constexpr bool formattable_char = + !std::is_same<decltype(arg), const unformattable_char&>::value; + static_assert(formattable_char, "Mixing character types is disallowed."); + + constexpr bool formattable_const = + !std::is_same<decltype(arg), const unformattable_const&>::value; + static_assert(formattable_const, "Cannot format a const argument."); + + // Formatting of arbitrary pointers is disallowed. If you want to output + // a pointer cast it to "void *" or "const void *". In particular, this + // forbids formatting of "[const] volatile char *" which is printed as bool + // by iostreams. + constexpr bool formattable_pointer = + !std::is_same<decltype(arg), const unformattable_pointer&>::value; + static_assert(formattable_pointer, + "Formatting of non-void pointers is disallowed."); + + constexpr bool formattable = + !std::is_same<decltype(arg), const unformattable&>::value; static_assert( - formattable<T>(), + formattable, "Cannot format an argument. To make type T formattable provide a " "formatter<T> specialization: https://fmt.dev/latest/api.html#udt"); - return 0; + return {arg}; } -template <typename T, typename U> inline const U& check(const U& val) { - return val; + +template <typename Context, typename T> +FMT_CONSTEXPR auto make_arg(T&& value) -> basic_format_arg<Context> { + basic_format_arg<Context> arg; + arg.type_ = mapped_type_constant<T, Context>::value; + arg.value_ = make_value<Context>(value); + return arg; } // The type template parameter is there to avoid an ODR violation when using @@ -1434,57 +1746,16 @@ template <typename T, typename U> inline const U& check(const U& val) { // another (not recommended). template <bool IS_PACKED, typename Context, type, typename T, FMT_ENABLE_IF(IS_PACKED)> -inline value<Context> make_arg(const T& val) { - return check<T>(arg_mapper<Context>().map(val)); +FMT_CONSTEXPR FMT_INLINE auto make_arg(T&& val) -> value<Context> { + return make_value<Context>(val); } template <bool IS_PACKED, typename Context, type, typename T, FMT_ENABLE_IF(!IS_PACKED)> -inline basic_format_arg<Context> make_arg(const T& value) { +FMT_CONSTEXPR inline auto make_arg(T&& value) -> basic_format_arg<Context> { return make_arg<Context>(value); } - -template <typename T> struct is_reference_wrapper : std::false_type {}; -template <typename T> -struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {}; - -template <typename T> const T& unwrap(const T& v) { return v; } -template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) { - return static_cast<const T&>(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 - // unit for placing vtable. So storage_node_base is made a fake template. - template <typename = void> struct node { - virtual ~node() = default; - std::unique_ptr<node<>> next; - }; - - template <typename T> struct typed_node : node<> { - T value; - - template <typename Arg> - FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {} - - template <typename Char> - FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg) - : value(arg.data(), arg.size()) {} - }; - - std::unique_ptr<node<>> head_; - - public: - template <typename T, typename Arg> const T& push(const Arg& arg) { - auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg)); - auto& value = new_node->value; - new_node->next = std::move(head_); - head_ = std::move(new_node); - return value; - } -}; -} // namespace detail +FMT_END_DETAIL_NAMESPACE // Formatting context. template <typename OutputIt, typename Char> class basic_format_context { @@ -1503,46 +1774,59 @@ template <typename OutputIt, typename Char> class basic_format_context { using parse_context_type = basic_format_parse_context<Char>; template <typename T> using formatter_type = formatter<T, char_type>; + basic_format_context(basic_format_context&&) = default; basic_format_context(const basic_format_context&) = delete; void operator=(const basic_format_context&) = delete; /** Constructs a ``basic_format_context`` object. References to the arguments are stored in the object so make sure they have appropriate lifetimes. */ - basic_format_context(OutputIt out, - basic_format_args<basic_format_context> ctx_args, - detail::locale_ref loc = detail::locale_ref()) + constexpr basic_format_context( + OutputIt out, basic_format_args<basic_format_context> ctx_args, + 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<char_type> name) { return args_.get(name); } - int arg_id(basic_string_view<char_type> name) { return args_.get_id(name); } - const basic_format_args<basic_format_context>& args() const { return args_; } + constexpr auto arg(int id) const -> format_arg { return args_.get(id); } + FMT_CONSTEXPR auto arg(basic_string_view<char_type> name) -> format_arg { + return args_.get(name); + } + FMT_CONSTEXPR auto arg_id(basic_string_view<char_type> name) -> int { + return args_.get_id(name); + } + auto args() const -> const basic_format_args<basic_format_context>& { + return args_; + } - detail::error_handler error_handler() { return {}; } + FMT_CONSTEXPR auto error_handler() -> detail::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_; } + FMT_CONSTEXPR auto out() -> iterator { return out_; } // Advances the begin iterator to ``it``. void advance_to(iterator it) { if (!detail::is_back_insert_iterator<iterator>()) out_ = it; } - detail::locale_ref locale() { return loc_; } + FMT_CONSTEXPR auto locale() -> detail::locale_ref { return loc_; } }; template <typename Char> using buffer_context = basic_format_context<detail::buffer_appender<Char>, Char>; using format_context = buffer_context<char>; -using wformat_context = buffer_context<wchar_t>; // Workaround an alias issue: https://stackoverflow.com/q/62767544/471164. #define FMT_BUFFER_CONTEXT(Char) \ basic_format_context<detail::buffer_appender<Char>, Char> +template <typename T, typename Char = char> +using is_formattable = bool_constant< + !std::is_base_of<detail::unformattable, + decltype(detail::arg_mapper<buffer_context<Char>>().map( + std::declval<T>()))>::value && + !detail::has_fallback_formatter<T, Char>::value>; + /** \rst An array of references to arguments. It can be implicitly converted into @@ -1579,14 +1863,16 @@ class format_arg_store : 0); public: - format_arg_store(const Args&... args) + template <typename... T> + FMT_CONSTEXPR FMT_INLINE format_arg_store(T&&... args) : #if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 basic_format_args<Context>(*this), #endif data_{detail::make_arg< is_packed, Context, - detail::mapped_type_constant<Args, Context>::value>(args)...} { + detail::mapped_type_constant<remove_cvref_t<T>, Context>::value>( + std::forward<T>(args))...} { detail::init_named_args(data_.named_args(), 0, 0, args...); } }; @@ -1600,36 +1886,16 @@ class format_arg_store \endrst */ template <typename Context = format_context, typename... Args> -inline format_arg_store<Context, Args...> make_format_args( - const Args&... args) { - return {args...}; -} - -/** - \rst - Constructs a `~fmt::format_arg_store` object that contains references - to arguments and can be implicitly converted to `~fmt::format_args`. - If ``format_str`` is a compile-time string then `make_args_checked` checks - its validity at compile time. - \endrst - */ -template <typename... Args, typename S, typename Char = char_t<S>> -inline auto make_args_checked(const S& format_str, - const remove_reference_t<Args>&... args) - -> format_arg_store<buffer_context<Char>, remove_reference_t<Args>...> { - static_assert( - detail::count<( - std::is_base_of<detail::view, remove_reference_t<Args>>::value && - std::is_reference<Args>::value)...>() == 0, - "passing views as lvalues is disallowed"); - detail::check_format_string<Args...>(format_str); - return {args...}; +constexpr auto make_format_args(Args&&... args) + -> format_arg_store<Context, remove_cvref_t<Args>...> { + return {std::forward<Args>(args)...}; } /** \rst - Returns a named argument to be used in a formatting function. It should only - be used in a call to a formatting function. + Returns a named argument to be used in a formatting function. + It should only be used in a call to a formatting function or + `dynamic_format_arg_store::push_back`. **Example**:: @@ -1637,186 +1903,13 @@ inline auto make_args_checked(const S& format_str, \endrst */ template <typename Char, typename T> -inline detail::named_arg<Char, T> arg(const Char* name, const T& arg) { +inline auto arg(const Char* name, const T& arg) -> detail::named_arg<Char, T> { static_assert(!detail::is_named_arg<T>(), "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 - into type-erased formatting functions such as `~fmt::vformat`. - \endrst - */ -template <typename Context> -class dynamic_format_arg_store -#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 - // Workaround a GCC template argument substitution bug. - : public basic_format_args<Context> -#endif -{ - private: - using char_type = typename Context::char_type; - - template <typename T> struct need_copy { - static constexpr detail::type mapped_type = - detail::mapped_type_constant<T, Context>::value; - - enum { - value = !(detail::is_reference_wrapper<T>::value || - std::is_same<T, basic_string_view<char_type>>::value || - std::is_same<T, detail::std_string_view<char_type>>::value || - (mapped_type != detail::type::cstring_type && - mapped_type != detail::type::string_type && - mapped_type != detail::type::custom_type)) - }; - }; - - template <typename T> - using stored_type = conditional_t<detail::is_string<T>::value, - std::basic_string<char_type>, T>; - - // Storage of basic_format_arg must be contiguous. - std::vector<basic_format_arg<Context>> data_; - std::vector<detail::named_arg_info<char_type>> named_info_; - - // Storage of arguments not fitting into basic_format_arg must grow - // without relocation because items in data_ refer to it. - detail::dynamic_arg_list dynamic_args_; - - friend class basic_format_args<Context>; - - unsigned long long get_types() const { - return detail::is_unpacked_bit | data_.size() | - (named_info_.empty() - ? 0ULL - : static_cast<unsigned long long>(detail::has_named_args_bit)); - } - - const basic_format_arg<Context>* data() const { - return named_info_.empty() ? data_.data() : data_.data() + 1; - } - - template <typename T> void emplace_arg(const T& arg) { - data_.emplace_back(detail::make_arg<Context>(arg)); - } - - template <typename T> - void emplace_arg(const detail::named_arg<char_type, T>& arg) { - if (named_info_.empty()) { - constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr}; - data_.insert(data_.begin(), {zero_ptr, 0}); - } - data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value))); - auto pop_one = [](std::vector<basic_format_arg<Context>>* data) { - data->pop_back(); - }; - std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)> - guard{&data_, pop_one}; - named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)}); - data_[0].value_.named_args = {named_info_.data(), named_info_.size()}; - guard.release(); - } - - public: - /** - \rst - Adds an argument into the dynamic store for later passing to a formatting - function. - - Note that custom types and string types (but not string views) are copied - into the store dynamically allocating memory if necessary. - - **Example**:: - - fmt::dynamic_format_arg_store<fmt::format_context> store; - store.push_back(42); - store.push_back("abc"); - store.push_back(1.5f); - std::string result = fmt::vformat("{} and {} and {}", store); - \endrst - */ - template <typename T> void push_back(const T& arg) { - if (detail::const_check(need_copy<T>::value)) - emplace_arg(dynamic_args_.push<stored_type<T>>(arg)); - else - emplace_arg(detail::unwrap(arg)); - } - - /** - \rst - Adds a reference to the argument into the dynamic store for later passing to - a formatting function. Supports named arguments wrapped in - ``std::reference_wrapper`` via ``std::ref()``/``std::cref()``. - - **Example**:: - - fmt::dynamic_format_arg_store<fmt::format_context> 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 <typename T> void push_back(std::reference_wrapper<T> arg) { - static_assert( - detail::is_named_arg<typename std::remove_cv<T>::type>::value || - need_copy<T>::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 formatting - function. ``std::reference_wrapper`` is supported to avoid copying of the - argument. - */ - template <typename T> - void push_back(const detail::named_arg<char_type, T>& arg) { - const char_type* arg_name = - dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str(); - if (detail::const_check(need_copy<T>::value)) { - emplace_arg( - fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(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_ = detail::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); - } -}; - -/** - \rst A view of a collection of formatting arguments. To avoid lifetime issues it should only be used as a parameter type in type-erased functions such as ``vformat``:: @@ -1846,25 +1939,27 @@ template <typename Context> class basic_format_args { const format_arg* args_; }; - bool is_packed() const { return (desc_ & detail::is_unpacked_bit) == 0; } - bool has_named_args() const { + constexpr auto is_packed() const -> bool { + return (desc_ & detail::is_unpacked_bit) == 0; + } + auto has_named_args() const -> bool { return (desc_ & detail::has_named_args_bit) != 0; } - detail::type type(int index) const { + FMT_CONSTEXPR auto type(int index) const -> detail::type { int shift = index * detail::packed_arg_bits; unsigned int mask = (1 << detail::packed_arg_bits) - 1; return static_cast<detail::type>((desc_ >> shift) & mask); } - basic_format_args(unsigned long long desc, - const detail::value<Context>* values) + constexpr FMT_INLINE basic_format_args(unsigned long long desc, + const detail::value<Context>* values) : desc_(desc), values_(values) {} - basic_format_args(unsigned long long desc, const format_arg* args) + constexpr basic_format_args(unsigned long long desc, const format_arg* args) : desc_(desc), args_(args) {} public: - basic_format_args() : desc_(0) {} + constexpr basic_format_args() : desc_(0), args_(nullptr) {} /** \rst @@ -1872,8 +1967,10 @@ template <typename Context> class basic_format_args { \endrst */ template <typename... Args> - FMT_INLINE basic_format_args(const format_arg_store<Context, Args...>& store) - : basic_format_args(store.desc, store.data_.args()) {} + constexpr FMT_INLINE basic_format_args( + const format_arg_store<Context, Args...>& store) + : basic_format_args(format_arg_store<Context, Args...>::desc, + store.data_.args()) {} /** \rst @@ -1881,7 +1978,8 @@ template <typename Context> class basic_format_args { `~fmt::dynamic_format_arg_store`. \endrst */ - FMT_INLINE basic_format_args(const dynamic_format_arg_store<Context>& store) + constexpr FMT_INLINE basic_format_args( + const dynamic_format_arg_store<Context>& store) : basic_format_args(store.get_types(), store.data()) {} /** @@ -1889,12 +1987,12 @@ template <typename Context> class basic_format_args { Constructs a `basic_format_args` object from a dynamic set of arguments. \endrst */ - basic_format_args(const format_arg* args, int count) + constexpr basic_format_args(const format_arg* args, int count) : basic_format_args(detail::is_unpacked_bit | detail::to_unsigned(count), args) {} /** Returns the argument with the specified id. */ - format_arg get(int id) const { + FMT_CONSTEXPR auto get(int id) const -> format_arg { format_arg arg; if (!is_packed()) { if (id < max_size()) arg = args_[id]; @@ -1907,12 +2005,14 @@ template <typename Context> class basic_format_args { return arg; } - template <typename Char> format_arg get(basic_string_view<Char> name) const { + template <typename Char> + auto get(basic_string_view<Char> name) const -> format_arg { int id = get_id(name); return id >= 0 ? get(id) : format_arg(); } - template <typename Char> int get_id(basic_string_view<Char> name) const { + template <typename Char> + auto get_id(basic_string_view<Char> name) const -> int { if (!has_named_args()) return -1; const auto& named_args = (is_packed() ? values_[-1] : args_[-1].value_).named_args; @@ -1922,87 +2022,1171 @@ template <typename Context> class basic_format_args { return -1; } - int max_size() const { + auto max_size() const -> int { unsigned long long max_packed = detail::max_packed_args; return static_cast<int>(is_packed() ? max_packed : desc_ & ~detail::is_unpacked_bit); } }; -#ifdef FMT_ARM_ABI_COMPATIBILITY /** An alias to ``basic_format_args<format_context>``. */ -// Separate types would result in shorter symbols but break ABI compatibility +// A separate type would result in shorter symbols but break ABI compatibility // between clang and gcc on ARM (#1919). using format_args = basic_format_args<format_context>; -using wformat_args = basic_format_args<wformat_context>; + +// We cannot use enum classes as bit fields because of a gcc bug, so we put them +// in namespaces instead (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414). +// Additionally, if an underlying type is specified, older gcc incorrectly warns +// that the type is too small. Both bugs are fixed in gcc 9.3. +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 903 +# define FMT_ENUM_UNDERLYING_TYPE(type) #else -// DEPRECATED! These are kept for ABI compatibility. -// It is a separate type rather than an alias to make symbols readable. -struct format_args : basic_format_args<format_context> { - template <typename... Args> - FMT_INLINE format_args(const Args&... args) : basic_format_args(args...) {} +# define FMT_ENUM_UNDERLYING_TYPE(type) : type +#endif +namespace align { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, left, right, center, + numeric}; +} +using align_t = align::type; +namespace sign { +enum type FMT_ENUM_UNDERLYING_TYPE(unsigned char){none, minus, plus, space}; +} +using sign_t = sign::type; + +FMT_BEGIN_DETAIL_NAMESPACE + +// Workaround an array initialization issue in gcc 4.8. +template <typename Char> struct fill_t { + private: + enum { max_size = 4 }; + Char data_[max_size] = {Char(' '), Char(0), Char(0), Char(0)}; + unsigned char size_ = 1; + + public: + FMT_CONSTEXPR void operator=(basic_string_view<Char> s) { + auto size = s.size(); + if (size > max_size) return throw_format_error("invalid fill"); + for (size_t i = 0; i < size; ++i) data_[i] = s[i]; + size_ = static_cast<unsigned char>(size); + } + + constexpr auto size() const -> size_t { return size_; } + constexpr auto data() const -> const Char* { return data_; } + + FMT_CONSTEXPR auto operator[](size_t index) -> Char& { return data_[index]; } + FMT_CONSTEXPR auto operator[](size_t index) const -> const Char& { + return data_[index]; + } +}; +FMT_END_DETAIL_NAMESPACE + +enum class presentation_type : unsigned char { + none, + // Integer types should go first, + dec, // 'd' + oct, // 'o' + hex_lower, // 'x' + hex_upper, // 'X' + bin_lower, // 'b' + bin_upper, // 'B' + hexfloat_lower, // 'a' + hexfloat_upper, // 'A' + exp_lower, // 'e' + exp_upper, // 'E' + fixed_lower, // 'f' + fixed_upper, // 'F' + general_lower, // 'g' + general_upper, // 'G' + chr, // 'c' + string, // 's' + pointer, // 'p' + debug // '?' +}; + +// Format specifiers for built-in and string types. +template <typename Char> struct basic_format_specs { + int width; + int precision; + presentation_type type; + align_t align : 4; + sign_t sign : 3; + bool alt : 1; // Alternate form ('#'). + bool localized : 1; + detail::fill_t<Char> fill; + + constexpr basic_format_specs() + : width(0), + precision(-1), + type(presentation_type::none), + align(align::none), + sign(sign::none), + alt(false), + localized(false) {} }; -struct wformat_args : basic_format_args<wformat_context> { - using basic_format_args::basic_format_args; + +using format_specs = basic_format_specs<char>; + +FMT_BEGIN_DETAIL_NAMESPACE + +enum class arg_id_kind { none, index, name }; + +// An argument reference. +template <typename Char> struct arg_ref { + FMT_CONSTEXPR arg_ref() : kind(arg_id_kind::none), val() {} + + FMT_CONSTEXPR explicit arg_ref(int index) + : kind(arg_id_kind::index), val(index) {} + FMT_CONSTEXPR explicit arg_ref(basic_string_view<Char> name) + : kind(arg_id_kind::name), val(name) {} + + FMT_CONSTEXPR auto operator=(int idx) -> arg_ref& { + kind = arg_id_kind::index; + val.index = idx; + return *this; + } + + arg_id_kind kind; + union value { + FMT_CONSTEXPR value(int id = 0) : index{id} {} + FMT_CONSTEXPR value(basic_string_view<Char> n) : name(n) {} + + int index; + basic_string_view<Char> name; + } val; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// different sets of arguments (precompilation of format strings). +template <typename Char> +struct dynamic_format_specs : basic_format_specs<Char> { + arg_ref<Char> width_ref; + arg_ref<Char> precision_ref; +}; + +struct auto_id {}; + +// A format specifier handler that sets fields in basic_format_specs. +template <typename Char> class specs_setter { + protected: + basic_format_specs<Char>& specs_; + + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char>& specs) + : specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter& other) + : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(align_t align) { specs_.align = align; } + FMT_CONSTEXPR void on_fill(basic_string_view<Char> fill) { + specs_.fill = fill; + } + FMT_CONSTEXPR void on_sign(sign_t s) { specs_.sign = s; } + FMT_CONSTEXPR void on_hash() { specs_.alt = true; } + FMT_CONSTEXPR void on_localized() { specs_.localized = true; } + + FMT_CONSTEXPR void on_zero() { + if (specs_.align == align::none) specs_.align = align::numeric; + specs_.fill[0] = Char('0'); + } + + FMT_CONSTEXPR void on_width(int width) { specs_.width = width; } + FMT_CONSTEXPR void on_precision(int precision) { + specs_.precision = precision; + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(presentation_type type) { specs_.type = type; } +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template <typename ParseContext> +class dynamic_specs_handler + : public specs_setter<typename ParseContext::char_type> { + public: + using char_type = typename ParseContext::char_type; + + FMT_CONSTEXPR dynamic_specs_handler(dynamic_format_specs<char_type>& specs, + ParseContext& ctx) + : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler& other) + : specs_setter<char_type>(other), + specs_(other.specs_), + context_(other.context_) {} + + template <typename Id> FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); + } + + template <typename Id> FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } + + private: + dynamic_format_specs<char_type>& specs_; + ParseContext& context_; + + using arg_ref_type = arg_ref<char_type>; + + FMT_CONSTEXPR auto make_arg_ref(int arg_id) -> arg_ref_type { + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR auto make_arg_ref(auto_id) -> arg_ref_type { + return arg_ref_type(context_.next_arg_id()); + } + + FMT_CONSTEXPR auto make_arg_ref(basic_string_view<char_type> arg_id) + -> arg_ref_type { + context_.check_arg_id(arg_id); + basic_string_view<char_type> format_str( + context_.begin(), to_unsigned(context_.end() - context_.begin())); + return arg_ref_type(arg_id); + } }; + +template <typename Char> constexpr bool is_ascii_letter(Char c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +// Converts a character to ASCII. Returns a number > 127 on conversion failure. +template <typename Char, FMT_ENABLE_IF(std::is_integral<Char>::value)> +constexpr auto to_ascii(Char c) -> Char { + return c; +} +template <typename Char, FMT_ENABLE_IF(std::is_enum<Char>::value)> +constexpr auto to_ascii(Char c) -> underlying_t<Char> { + return c; +} + +template <typename Char> +FMT_CONSTEXPR auto code_point_length(const Char* begin) -> int { + if (const_check(sizeof(Char) != 1)) return 1; + auto lengths = + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0\0\0\2\2\2\2\3\3\4"; + int len = lengths[static_cast<unsigned char>(*begin) >> 3]; + + // Compute the pointer to the next character early so that the next + // iteration can start working on the next character. Neither Clang + // nor GCC figure out this reordering on their own. + return len + !len; +} + +// Return the result via the out param to workaround gcc bug 77539. +template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*> +FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool { + for (out = first; out != last; ++out) { + if (*out == value) return true; + } + return false; +} + +template <> +inline auto find<false, char>(const char* first, const char* last, char value, + const char*& out) -> bool { + out = static_cast<const char*>( + std::memchr(first, value, to_unsigned(last - first))); + return out != nullptr; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template <typename Char> +FMT_CONSTEXPR auto parse_nonnegative_int(const Char*& begin, const Char* end, + int error_value) noexcept -> int { + FMT_ASSERT(begin != end && '0' <= *begin && *begin <= '9', ""); + unsigned value = 0, prev = 0; + auto p = begin; + do { + prev = value; + value = value * 10 + unsigned(*p - '0'); + ++p; + } while (p != end && '0' <= *p && *p <= '9'); + auto num_digits = p - begin; + begin = p; + if (num_digits <= std::numeric_limits<int>::digits10) + return static_cast<int>(value); + // Check for overflow. + const unsigned max = to_unsigned((std::numeric_limits<int>::max)()); + return num_digits == std::numeric_limits<int>::digits10 + 1 && + prev * 10ull + unsigned(p[-1] - '0') <= max + ? static_cast<int>(value) + : error_value; +} + +// Parses fill and alignment. +template <typename Char, typename Handler> +FMT_CONSTEXPR auto parse_align(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + auto align = align::none; + auto p = begin + code_point_length(begin); + if (end - p <= 0) p = begin; + for (;;) { + switch (to_ascii(*p)) { + case '<': + align = align::left; + break; + case '>': + align = align::right; + break; + case '^': + align = align::center; + break; + default: + break; + } + if (align != align::none) { + if (p != begin) { + auto c = *begin; + if (c == '{') + return handler.on_error("invalid fill character '{'"), begin; + handler.on_fill(basic_string_view<Char>(begin, to_unsigned(p - begin))); + begin = p + 1; + } else + ++begin; + handler.on_align(align); + break; + } else if (p == begin) { + break; + } + p = begin; + } + return begin; +} + +template <typename Char> FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +template <typename Char, typename IDHandler> +FMT_CONSTEXPR auto do_parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) -> const Char* { + FMT_ASSERT(begin != end, ""); + Char c = *begin; + if (c >= '0' && c <= '9') { + int index = 0; + if (c != '0') + index = + parse_nonnegative_int(begin, end, (std::numeric_limits<int>::max)()); + else + ++begin; + if (begin == end || (*begin != '}' && *begin != ':')) + handler.on_error("invalid format string"); + else + handler(index); + return begin; + } + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return begin; + } + auto it = begin; + do { + ++it; + } while (it != end && (is_name_start(c = *it) || ('0' <= c && c <= '9'))); + handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); + return it; +} + +template <typename Char, typename IDHandler> +FMT_CONSTEXPR FMT_INLINE auto parse_arg_id(const Char* begin, const Char* end, + IDHandler&& handler) -> const Char* { + Char c = *begin; + if (c != '}' && c != ':') return do_parse_arg_id(begin, end, handler); + handler(); + return begin; +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR auto parse_width(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct width_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_width(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + FMT_ASSERT(begin != end, ""); + if ('0' <= *begin && *begin <= '9') { + int width = parse_nonnegative_int(begin, end, -1); + if (width != -1) + handler.on_width(width); + else + handler.on_error("number is too big"); + } else if (*begin == '{') { + ++begin; + if (begin != end) begin = parse_arg_id(begin, end, width_adapter{handler}); + if (begin == end || *begin != '}') + return handler.on_error("invalid format string"), begin; + ++begin; + } + return begin; +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR auto parse_precision(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + using detail::auto_id; + struct precision_adapter { + Handler& handler; + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(int id) { handler.on_dynamic_precision(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + ++begin; + auto c = begin != end ? *begin : Char(); + if ('0' <= c && c <= '9') { + auto precision = parse_nonnegative_int(begin, end, -1); + if (precision != -1) + handler.on_precision(precision); + else + handler.on_error("number is too big"); + } else if (c == '{') { + ++begin; + if (begin != end) + begin = parse_arg_id(begin, end, precision_adapter{handler}); + if (begin == end || *begin++ != '}') + return handler.on_error("invalid format string"), begin; + } else { + return handler.on_error("missing precision specifier"), begin; + } + handler.end_precision(); + return begin; +} + +template <typename Char> +FMT_CONSTEXPR auto parse_presentation_type(Char type) -> presentation_type { + switch (to_ascii(type)) { + case 'd': + return presentation_type::dec; + case 'o': + return presentation_type::oct; + case 'x': + return presentation_type::hex_lower; + case 'X': + return presentation_type::hex_upper; + case 'b': + return presentation_type::bin_lower; + case 'B': + return presentation_type::bin_upper; + case 'a': + return presentation_type::hexfloat_lower; + case 'A': + return presentation_type::hexfloat_upper; + case 'e': + return presentation_type::exp_lower; + case 'E': + return presentation_type::exp_upper; + case 'f': + return presentation_type::fixed_lower; + case 'F': + return presentation_type::fixed_upper; + case 'g': + return presentation_type::general_lower; + case 'G': + return presentation_type::general_upper; + case 'c': + return presentation_type::chr; + case 's': + return presentation_type::string; + case 'p': + return presentation_type::pointer; + case '?': + return presentation_type::debug; + default: + return presentation_type::none; + } +} + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +template <typename Char, typename SpecHandler> +FMT_CONSTEXPR FMT_INLINE auto parse_format_specs(const Char* begin, + const Char* end, + SpecHandler&& handler) + -> const Char* { + if (1 < end - begin && begin[1] == '}' && is_ascii_letter(*begin) && + *begin != 'L') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + return begin; + } + + if (begin == end) return begin; + + begin = parse_align(begin, end, handler); + if (begin == end) return begin; + + // Parse sign. + switch (to_ascii(*begin)) { + case '+': + handler.on_sign(sign::plus); + ++begin; + break; + case '-': + handler.on_sign(sign::minus); + ++begin; + break; + case ' ': + handler.on_sign(sign::space); + ++begin; + break; + default: + break; + } + if (begin == end) return begin; + + if (*begin == '#') { + handler.on_hash(); + if (++begin == end) return begin; + } + + // Parse zero flag. + if (*begin == '0') { + handler.on_zero(); + if (++begin == end) return begin; + } + + begin = parse_width(begin, end, handler); + if (begin == end) return begin; + + // Parse precision. + if (*begin == '.') { + begin = parse_precision(begin, end, handler); + if (begin == end) return begin; + } + + if (*begin == 'L') { + handler.on_localized(); + ++begin; + } + + // Parse type. + if (begin != end && *begin != '}') { + presentation_type type = parse_presentation_type(*begin++); + if (type == presentation_type::none) + handler.on_error("invalid type specifier"); + handler.on_type(type); + } + return begin; +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR auto parse_replacement_field(const Char* begin, const Char* end, + Handler&& handler) -> const Char* { + struct id_adapter { + Handler& handler; + int arg_id; + + FMT_CONSTEXPR void operator()() { arg_id = handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(int id) { arg_id = handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + arg_id = handler.on_arg_id(id); + } + FMT_CONSTEXPR void on_error(const char* message) { + if (message) handler.on_error(message); + } + }; + + ++begin; + if (begin == end) return handler.on_error("invalid format string"), end; + if (*begin == '}') { + handler.on_replacement_field(handler.on_arg_id(), begin); + } else if (*begin == '{') { + handler.on_text(begin, begin + 1); + } else { + auto adapter = id_adapter{handler, 0}; + begin = parse_arg_id(begin, end, adapter); + Char c = begin != end ? *begin : Char(); + if (c == '}') { + handler.on_replacement_field(adapter.arg_id, begin); + } else if (c == ':') { + begin = handler.on_format_specs(adapter.arg_id, begin + 1, end); + if (begin == end || *begin != '}') + return handler.on_error("unknown format specifier"), end; + } else { + return handler.on_error("missing '}' in format string"), end; + } + } + return begin + 1; +} + +template <bool IS_CONSTEXPR, typename Char, typename Handler> +FMT_CONSTEXPR FMT_INLINE void parse_format_string( + basic_string_view<Char> format_str, Handler&& handler) { + // Workaround a name-lookup bug in MSVC's modules implementation. + using detail::find; + + auto begin = format_str.data(); + auto end = begin + format_str.size(); + if (end - begin < 32) { + // Use a simple loop instead of memchr for small strings. + const Char* p = begin; + while (p != end) { + auto c = *p++; + if (c == '{') { + handler.on_text(begin, p - 1); + begin = p = parse_replacement_field(p - 1, end, handler); + } else if (c == '}') { + if (p == end || *p != '}') + return handler.on_error("unmatched '}' in format string"); + handler.on_text(begin, p); + begin = ++p; + } + } + handler.on_text(begin, end); + return; + } + struct writer { + FMT_CONSTEXPR void operator()(const Char* from, const Char* to) { + if (from == to) return; + for (;;) { + const Char* p = nullptr; + if (!find<IS_CONSTEXPR>(from, to, Char('}'), p)) + return handler_.on_text(from, to); + ++p; + if (p == to || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(from, p); + from = p + 1; + } + } + Handler& handler_; + } write = {handler}; + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char* p = begin; + if (*begin != '{' && !find<IS_CONSTEXPR>(begin + 1, end, Char('{'), p)) + return write(begin, end); + write(begin, p); + begin = parse_replacement_field(p, end, handler); + } +} + +template <typename T, bool = is_named_arg<T>::value> struct strip_named_arg { + using type = T; +}; +template <typename T> struct strip_named_arg<T, true> { + using type = remove_cvref_t<decltype(T::value)>; +}; + +template <typename T, typename ParseContext> +FMT_CONSTEXPR auto parse_format_specs(ParseContext& ctx) + -> decltype(ctx.begin()) { + using char_type = typename ParseContext::char_type; + using context = buffer_context<char_type>; + using stripped_type = typename strip_named_arg<T>::type; + using mapped_type = conditional_t< + mapped_type_constant<T, context>::value != type::custom_type, + decltype(arg_mapper<context>().map(std::declval<const T&>())), + stripped_type>; + auto f = conditional_t<has_formatter<mapped_type, context>::value, + formatter<mapped_type, char_type>, + fallback_formatter<stripped_type, char_type>>(); + return f.parse(ctx); +} + +template <typename ErrorHandler> +FMT_CONSTEXPR void check_int_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type > presentation_type::bin_upper && type != presentation_type::chr) + eh.on_error("invalid type specifier"); +} + +// Checks char specs and returns true if the type spec is char (and not int). +template <typename Char, typename ErrorHandler = error_handler> +FMT_CONSTEXPR auto check_char_specs(const basic_format_specs<Char>& specs, + ErrorHandler&& eh = {}) -> bool { + if (specs.type != presentation_type::none && + specs.type != presentation_type::chr && + specs.type != presentation_type::debug) { + check_int_type_spec(specs.type, eh); + return false; + } + if (specs.align == align::numeric || specs.sign != sign::none || specs.alt) + eh.on_error("invalid format specifier for char"); + return true; +} + +// A floating-point presentation format. +enum class float_format : unsigned char { + general, // General: exponent notation or fixed point based on magnitude. + exp, // Exponent notation with the default precision of 6, e.g. 1.2e-3. + fixed, // Fixed point with the default precision of 6, e.g. 0.0012. + hex +}; + +struct float_specs { + int precision; + float_format format : 8; + sign_t sign : 8; + bool upper : 1; + bool locale : 1; + bool binary32 : 1; + bool showpoint : 1; +}; + +template <typename ErrorHandler = error_handler, typename Char> +FMT_CONSTEXPR auto parse_float_type_spec(const basic_format_specs<Char>& specs, + ErrorHandler&& eh = {}) + -> float_specs { + auto result = float_specs(); + result.showpoint = specs.alt; + result.locale = specs.localized; + switch (specs.type) { + case presentation_type::none: + result.format = float_format::general; + break; + case presentation_type::general_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::general_lower: + result.format = float_format::general; + break; + case presentation_type::exp_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::exp_lower: + result.format = float_format::exp; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::fixed_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::fixed_lower: + result.format = float_format::fixed; + result.showpoint |= specs.precision != 0; + break; + case presentation_type::hexfloat_upper: + result.upper = true; + FMT_FALLTHROUGH; + case presentation_type::hexfloat_lower: + result.format = float_format::hex; + break; + default: + eh.on_error("invalid type specifier"); + break; + } + return result; +} + +template <typename ErrorHandler = error_handler> +FMT_CONSTEXPR auto check_cstring_type_spec(presentation_type type, + ErrorHandler&& eh = {}) -> bool { + if (type == presentation_type::none || type == presentation_type::string) + return true; + if (type != presentation_type::pointer) eh.on_error("invalid type specifier"); + return false; +} + +template <typename ErrorHandler = error_handler> +FMT_CONSTEXPR void check_string_type_spec(presentation_type type, + ErrorHandler&& eh = {}) { + if (type != presentation_type::none && type != presentation_type::string && + type != presentation_type::debug) + eh.on_error("invalid type specifier"); +} + +template <typename ErrorHandler> +FMT_CONSTEXPR void check_pointer_type_spec(presentation_type type, + ErrorHandler&& eh) { + if (type != presentation_type::none && type != presentation_type::pointer) + eh.on_error("invalid type specifier"); +} + +// A parse_format_specs handler that checks if specifiers are consistent with +// the argument type. +template <typename Handler> class specs_checker : public Handler { + private: + detail::type arg_type_; + + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic_type(arg_type_)) + this->on_error("format specifier requires numeric argument"); + } + + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, detail::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + FMT_CONSTEXPR void on_align(align_t align) { + if (align == align::numeric) require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_sign(sign_t s) { + require_numeric_argument(); + if (is_integral_type(arg_type_) && arg_type_ != type::int_type && + arg_type_ != type::long_long_type && arg_type_ != type::int128_type && + arg_type_ != type::char_type) { + this->on_error("format specifier requires signed argument"); + } + Handler::on_sign(s); + } + + FMT_CONSTEXPR void on_hash() { + require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_localized() { + require_numeric_argument(); + Handler::on_localized(); + } + + FMT_CONSTEXPR void on_zero() { + require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() { + if (is_integral_type(arg_type_) || arg_type_ == type::pointer_type) + this->on_error("precision not allowed for this argument type"); + } +}; + +constexpr int invalid_arg_index = -1; + +#if FMT_USE_NONTYPE_TEMPLATE_ARGS +template <int N, typename T, typename... Args, typename Char> +constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int { + if constexpr (detail::is_statically_named_arg<T>()) { + if (name == T::name) return N; + } + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<N + 1, Args...>(name); + (void)name; // Workaround an MSVC bug about "unused" parameter. + return invalid_arg_index; +} #endif -namespace detail { +template <typename... Args, typename Char> +FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + if constexpr (sizeof...(Args) > 0) + return get_arg_index_by_name<0, Args...>(name); +#endif + (void)name; + return invalid_arg_index; +} + +template <typename Char, typename ErrorHandler, typename... Args> +class format_string_checker { + private: + // In the future basic_format_parse_context will replace compile_parse_context + // here and will use is_constant_evaluated and downcasting to access the data + // needed for compile-time checks: https://godbolt.org/z/GvWzcTjh1. + using parse_context_type = compile_parse_context<Char, ErrorHandler>; + static constexpr int num_args = sizeof...(Args); + + // Format specifier parsing function. + using parse_func = const Char* (*)(parse_context_type&); + + parse_context_type context_; + parse_func parse_funcs_[num_args > 0 ? static_cast<size_t>(num_args) : 1]; + type types_[num_args > 0 ? static_cast<size_t>(num_args) : 1]; + + public: + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view<Char> format_str, ErrorHandler eh) + : context_(format_str, num_args, types_, eh), + parse_funcs_{&parse_format_specs<Args, parse_context_type>...}, + types_{type_constant<Args, char>::value...} {} + + FMT_CONSTEXPR void on_text(const Char*, const Char*) {} + + FMT_CONSTEXPR auto on_arg_id() -> int { return context_.next_arg_id(); } + FMT_CONSTEXPR auto on_arg_id(int id) -> int { + return context_.check_arg_id(id), id; + } + FMT_CONSTEXPR auto on_arg_id(basic_string_view<Char> id) -> int { +#if FMT_USE_NONTYPE_TEMPLATE_ARGS + auto index = get_arg_index_by_name<Args...>(id); + if (index == invalid_arg_index) on_error("named argument is not found"); + return context_.check_arg_id(index), index; +#else + (void)id; + on_error("compile-time checks for named arguments require C++20 support"); + return 0; +#endif + } -template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)> -std::basic_string<Char> vformat( - basic_string_view<Char> format_str, - basic_format_args<buffer_context<type_identity_t<Char>>> args); + FMT_CONSTEXPR void on_replacement_field(int, const Char*) {} + + FMT_CONSTEXPR auto on_format_specs(int id, const Char* begin, const Char*) + -> const Char* { + context_.advance_to(context_.begin() + (begin - &*context_.begin())); + // id >= 0 check is a workaround for gcc 10 bug (#2065). + return id >= 0 && id < num_args ? parse_funcs_[id](context_) : begin; + } -FMT_API std::string vformat(string_view format_str, format_args args); + FMT_CONSTEXPR void on_error(const char* message) { + context_.on_error(message); + } +}; + +// Reports a compile-time error if S is not a valid format string. +template <typename..., typename S, FMT_ENABLE_IF(!is_compile_string<S>::value)> +FMT_INLINE void check_format_string(const S&) { +#ifdef FMT_ENFORCE_COMPILE_STRING + static_assert(is_compile_string<S>::value, + "FMT_ENFORCE_COMPILE_STRING requires all format strings to use " + "FMT_STRING."); +#endif +} +template <typename... Args, typename S, + FMT_ENABLE_IF(is_compile_string<S>::value)> +void check_format_string(S format_str) { + FMT_CONSTEXPR auto s = basic_string_view<typename S::char_type>(format_str); + using checker = format_string_checker<typename S::char_type, error_handler, + remove_cvref_t<Args>...>; + FMT_CONSTEXPR bool invalid_format = + (parse_format_string<true>(s, checker(s, {})), true); + ignore_unused(invalid_format); +} template <typename Char> void vformat_to( - buffer<Char>& buf, basic_string_view<Char> format_str, + buffer<Char>& buf, basic_string_view<Char> fmt, basic_format_args<FMT_BUFFER_CONTEXT(type_identity_t<Char>)> args, - detail::locale_ref loc = {}); - -template <typename Char, typename Args, - FMT_ENABLE_IF(!std::is_same<Char, char>::value)> -inline void vprint_mojibake(std::FILE*, basic_string_view<Char>, const Args&) {} + locale_ref loc = {}); FMT_API void vprint_mojibake(std::FILE*, string_view, format_args); #ifndef _WIN32 inline void vprint_mojibake(std::FILE*, string_view, format_args) {} #endif -} // namespace detail +FMT_END_DETAIL_NAMESPACE + +// A formatter specialization for the core types corresponding to detail::type +// constants. +template <typename T, typename Char> +struct formatter<T, Char, + enable_if_t<detail::type_constant<T, Char>::value != + detail::type::custom_type>> { + private: + detail::dynamic_format_specs<Char> specs_; + + public: + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + auto begin = ctx.begin(), end = ctx.end(); + if (begin == end) return begin; + using handler_type = detail::dynamic_specs_handler<ParseContext>; + auto type = detail::type_constant<T, Char>::value; + auto checker = + detail::specs_checker<handler_type>(handler_type(specs_, ctx), type); + auto it = detail::parse_format_specs(begin, end, checker); + auto eh = ctx.error_handler(); + switch (type) { + case detail::type::none_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case detail::type::bool_type: + if (specs_.type == presentation_type::none || + specs_.type == presentation_type::string) { + break; + } + FMT_FALLTHROUGH; + case detail::type::int_type: + case detail::type::uint_type: + case detail::type::long_long_type: + case detail::type::ulong_long_type: + case detail::type::int128_type: + case detail::type::uint128_type: + detail::check_int_type_spec(specs_.type, eh); + break; + case detail::type::char_type: + detail::check_char_specs(specs_, eh); + break; + case detail::type::float_type: + if (detail::const_check(FMT_USE_FLOAT)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "float support disabled"); + break; + case detail::type::double_type: + if (detail::const_check(FMT_USE_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "double support disabled"); + break; + case detail::type::long_double_type: + if (detail::const_check(FMT_USE_LONG_DOUBLE)) + detail::parse_float_type_spec(specs_, eh); + else + FMT_ASSERT(false, "long double support disabled"); + break; + case detail::type::cstring_type: + detail::check_cstring_type_spec(specs_.type, eh); + break; + case detail::type::string_type: + detail::check_string_type_spec(specs_.type, eh); + break; + case detail::type::pointer_type: + detail::check_pointer_type_spec(specs_.type, eh); + break; + case detail::type::custom_type: + // Custom format specifiers are checked in parse functions of + // formatter specializations. + break; + } + return it; + } + + template <typename FormatContext> + FMT_CONSTEXPR auto format(const T& val, FormatContext& ctx) const + -> decltype(ctx.out()); +}; + +#define FMT_FORMAT_AS(Type, Base) \ + template <typename Char> \ + struct formatter<Type, Char> : formatter<Base, Char> { \ + template <typename FormatContext> \ + auto format(Type const& val, FormatContext& ctx) const \ + -> decltype(ctx.out()) { \ + return formatter<Base, Char>::format(static_cast<Base>(val), ctx); \ + } \ + } + +FMT_FORMAT_AS(signed char, int); +FMT_FORMAT_AS(unsigned char, unsigned); +FMT_FORMAT_AS(short, int); +FMT_FORMAT_AS(unsigned short, unsigned); +FMT_FORMAT_AS(long, long long); +FMT_FORMAT_AS(unsigned long, unsigned long long); +FMT_FORMAT_AS(Char*, const Char*); +FMT_FORMAT_AS(std::basic_string<Char>, basic_string_view<Char>); +FMT_FORMAT_AS(std::nullptr_t, const void*); +FMT_FORMAT_AS(detail::std_string_view<Char>, basic_string_view<Char>); + +template <typename Char> struct basic_runtime { basic_string_view<Char> str; }; + +/** A compile-time format string. */ +template <typename Char, typename... Args> class basic_format_string { + private: + basic_string_view<Char> str_; + + public: + template <typename S, + FMT_ENABLE_IF( + std::is_convertible<const S&, basic_string_view<Char>>::value)> + FMT_CONSTEVAL FMT_INLINE basic_format_string(const S& s) : str_(s) { + static_assert( + detail::count< + (std::is_base_of<detail::view, remove_reference_t<Args>>::value && + std::is_reference<Args>::value)...>() == 0, + "passing views as lvalues is disallowed"); +#ifdef FMT_HAS_CONSTEVAL + if constexpr (detail::count_named_args<Args...>() == + detail::count_statically_named_args<Args...>()) { + using checker = detail::format_string_checker<Char, detail::error_handler, + remove_cvref_t<Args>...>; + detail::parse_format_string<true>(str_, checker(s, {})); + } +#else + detail::check_format_string<Args...>(s); +#endif + } + basic_format_string(basic_runtime<Char> r) : str_(r.str) {} + + FMT_INLINE operator basic_string_view<Char>() const { return str_; } +}; + +#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409 +// Workaround broken conversion on older gcc. +template <typename...> using format_string = string_view; +inline auto runtime(string_view s) -> basic_string_view<char> { return s; } +#else +template <typename... Args> +using format_string = basic_format_string<char, type_identity_t<Args>...>; +/** + \rst + Creates a runtime format string. + + **Example**:: + + // Check format string at runtime instead of compile-time. + fmt::print(fmt::runtime("{:d}"), "I am not a number"); + \endrst + */ +inline auto runtime(string_view s) -> basic_runtime<char> { return {{s}}; } +#endif + +FMT_API auto vformat(string_view fmt, format_args args) -> std::string; + +/** + \rst + Formats ``args`` according to specifications in ``fmt`` and returns the result + as a string. + + **Example**:: + + #include <fmt/core.h> + std::string message = fmt::format("The answer is {}.", 42); + \endrst +*/ +template <typename... T> +FMT_NODISCARD FMT_INLINE auto format(format_string<T...> fmt, T&&... args) + -> std::string { + return vformat(fmt, fmt::make_format_args(args...)); +} /** Formats a string and writes the output to ``out``. */ -// GCC 8 and earlier cannot handle std::back_insert_iterator<Container> with -// vformat_to<ArgFormatter>(...) overload, so SFINAE on iterator type instead. -template <typename OutputIt, typename S, typename Char = char_t<S>, - bool enable = detail::is_output_iterator<OutputIt, Char>::value> -auto vformat_to(OutputIt out, const S& format_str, - basic_format_args<buffer_context<type_identity_t<Char>>> args) - -> typename std::enable_if<enable, OutputIt>::type { - decltype(detail::get_buffer<Char>(out)) buf(detail::get_buffer_init(out)); - detail::vformat_to(buf, to_string_view(format_str), args); +template <typename OutputIt, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> +auto vformat_to(OutputIt out, string_view fmt, format_args args) -> OutputIt { + using detail::get_buffer; + auto&& buf = get_buffer<char>(out); + detail::vformat_to(buf, fmt, args, {}); return detail::get_iterator(buf); } /** \rst - Formats arguments, writes the result to the output iterator ``out`` and returns - the iterator past the end of the output range. + Formats ``args`` according to specifications in ``fmt``, writes the result to + the output iterator ``out`` and returns the iterator past the end of the output + range. `format_to` does not append a terminating null character. **Example**:: - std::vector<char> out; + auto out = std::vector<char>(); fmt::format_to(std::back_inserter(out), "{}", 42); \endrst */ -// We cannot use FMT_ENABLE_IF because of a bug in gcc 8.3. -template <typename OutputIt, typename S, typename... Args, - bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value> -inline auto format_to(OutputIt out, const S& format_str, Args&&... args) -> - typename std::enable_if<enable, OutputIt>::type { - const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); - return vformat_to(out, to_string_view(format_str), vargs); +template <typename OutputIt, typename... T, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> +FMT_INLINE auto format_to(OutputIt out, format_string<T...> fmt, T&&... args) + -> OutputIt { + return vformat_to(out, fmt, fmt::make_format_args(args...)); } template <typename OutputIt> struct format_to_n_result { @@ -2012,111 +3196,82 @@ template <typename OutputIt> struct format_to_n_result { size_t size; }; -template <typename OutputIt, typename Char, typename... Args, - FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)> -inline format_to_n_result<OutputIt> vformat_to_n( - OutputIt out, size_t n, basic_string_view<Char> format_str, - basic_format_args<buffer_context<type_identity_t<Char>>> args) { - detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out, - n); - detail::vformat_to(buf, format_str, args); +template <typename OutputIt, typename... T, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> +auto vformat_to_n(OutputIt out, size_t n, string_view fmt, format_args args) + -> format_to_n_result<OutputIt> { + using traits = detail::fixed_buffer_traits; + auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n); + detail::vformat_to(buf, fmt, args, {}); return {buf.out(), buf.count()}; } /** - \rst - Formats arguments, writes up to ``n`` characters of the result to the output - iterator ``out`` and returns the total output size and the iterator past the - end of the output range. - \endrst + \rst + Formats ``args`` according to specifications in ``fmt``, writes up to ``n`` + characters of the result to the output iterator ``out`` and returns the total + (not truncated) output size and the iterator past the end of the output range. + `format_to_n` does not append a terminating null character. + \endrst */ -template <typename OutputIt, typename S, typename... Args, - bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value> -inline auto format_to_n(OutputIt out, size_t n, const S& format_str, - const Args&... args) -> - typename std::enable_if<enable, format_to_n_result<OutputIt>>::type { - const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); - return vformat_to_n(out, n, to_string_view(format_str), vargs); +template <typename OutputIt, typename... T, + FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)> +FMT_INLINE auto format_to_n(OutputIt out, size_t n, format_string<T...> fmt, + T&&... args) -> format_to_n_result<OutputIt> { + return vformat_to_n(out, n, fmt, fmt::make_format_args(args...)); } -/** - Returns the number of characters in the output of - ``format(format_str, args...)``. - */ -template <typename... Args> -inline size_t formatted_size(string_view format_str, Args&&... args) { - const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); - detail::counting_buffer<> buf; - detail::vformat_to(buf, format_str, vargs); +/** Returns the number of chars in the output of ``format(fmt, args...)``. */ +template <typename... T> +FMT_NODISCARD FMT_INLINE auto formatted_size(format_string<T...> fmt, + T&&... args) -> size_t { + auto buf = detail::counting_buffer<>(); + detail::vformat_to(buf, string_view(fmt), fmt::make_format_args(args...), {}); return buf.count(); } -template <typename S, typename Char = char_t<S>> -FMT_INLINE std::basic_string<Char> vformat( - const S& format_str, - basic_format_args<buffer_context<type_identity_t<Char>>> args) { - return detail::vformat(to_string_view(format_str), args); -} +FMT_API void vprint(string_view fmt, format_args args); +FMT_API void vprint(std::FILE* f, string_view fmt, format_args args); /** \rst - Formats arguments and returns the result as a string. + Formats ``args`` according to specifications in ``fmt`` and writes the output + to ``stdout``. **Example**:: - #include <fmt/core.h> - std::string message = fmt::format("The answer is {}", 42); + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); \endrst -*/ -// Pass char_t as a default template parameter instead of using -// std::basic_string<char_t<S>> to reduce the symbol size. -template <typename S, typename... Args, typename Char = char_t<S>> -FMT_INLINE std::basic_string<Char> format(const S& format_str, Args&&... args) { - const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); - return detail::vformat(to_string_view(format_str), vargs); + */ +template <typename... T> +FMT_INLINE void print(format_string<T...> fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::is_utf8() ? vprint(fmt, vargs) + : detail::vprint_mojibake(stdout, fmt, vargs); } -FMT_API void vprint(string_view, format_args); -FMT_API void vprint(std::FILE*, string_view, format_args); - /** \rst - Formats ``args`` according to specifications in ``format_str`` and writes the - output to the file ``f``. Strings are assumed to be Unicode-encoded unless the - ``FMT_UNICODE`` macro is set to 0. + Formats ``args`` according to specifications in ``fmt`` and writes the + output to the file ``f``. **Example**:: fmt::print(stderr, "Don't {}!", "panic"); \endrst */ -template <typename S, typename... Args, typename Char = char_t<S>> -inline void print(std::FILE* f, const S& format_str, Args&&... args) { - const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); - return detail::is_unicode<Char>() - ? vprint(f, to_string_view(format_str), vargs) - : detail::vprint_mojibake(f, to_string_view(format_str), vargs); +template <typename... T> +FMT_INLINE void print(std::FILE* f, format_string<T...> fmt, T&&... args) { + const auto& vargs = fmt::make_format_args(args...); + return detail::is_utf8() ? vprint(f, fmt, vargs) + : detail::vprint_mojibake(f, fmt, vargs); } -/** - \rst - Formats ``args`` according to specifications in ``format_str`` and writes - the output to ``stdout``. Strings are assumed to be Unicode-encoded unless - the ``FMT_UNICODE`` macro is set to 0. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template <typename S, typename... Args, typename Char = char_t<S>> -inline void print(const S& format_str, Args&&... args) { - const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...); - return detail::is_unicode<Char>() - ? vprint(to_string_view(format_str), vargs) - : detail::vprint_mojibake(stdout, to_string_view(format_str), - vargs); -} +FMT_MODULE_EXPORT_END +FMT_GCC_PRAGMA("GCC pop_options") FMT_END_NAMESPACE +#ifdef FMT_HEADER_ONLY +# include "format.h" +#endif #endif // FMT_CORE_H_ |