From 1653244c6990fd58b753971879ece30e3287e655 Mon Sep 17 00:00:00 2001 From: mocabe Date: Tue, 4 Jun 2019 00:34:27 +0900 Subject: [PATCH 01/11] Fix compilation issue on VS2019 (#1186) (#1191) --- include/fmt/core.h | 5 +++-- include/fmt/format-inl.h | 3 ++- include/fmt/format.h | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 58c716d85f15..7e22c166558a 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -204,9 +204,10 @@ FMT_BEGIN_NAMESPACE template using enable_if_t = typename std::enable_if::type; -// An enable_if helper to be used in template parameters which results in much +// enable_if helpers to be used in template parameters which results in much // shorter symbols: https://godbolt.org/z/sWw4vP. -#define FMT_ENABLE_IF(...) enable_if_t<__VA_ARGS__, int> = 0 +// Also include fix for VS2019 compilation issue (see #1140 and #1186). +#define FMT_ENABLE_IF(...) enable_if_t<(__VA_ARGS__), int> = 0 namespace internal { diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index fa5eb6f3610c..8778337eba82 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -674,7 +674,8 @@ template struct grisu_shortest_handler { } }; -template > +template > FMT_API bool grisu_format(Double value, buffer& buf, int precision, unsigned options, int& exp) { FMT_ASSERT(value >= 0, "value is negative"); diff --git a/include/fmt/format.h b/include/fmt/format.h index 82f7092d00c1..f1c623cc1113 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2246,7 +2246,7 @@ FMT_CONSTEXPR bool do_check_format_string(basic_string_view s, } template ::value, int>> + enable_if_t<(is_compile_string::value), int>> void check_format_string(S format_str) { typedef typename S::char_type char_t; FMT_CONSTEXPR_DECL bool invalid_format = From 0c6a6e0250a01ad2458d47c2e42f4403665cd36c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Sun, 2 Jun 2019 19:29:49 -0700 Subject: [PATCH 02/11] Get rid of the FILE* hack and reword apidocs --- doc/api.rst | 2 ++ include/fmt/color.h | 7 ------- include/fmt/core.h | 31 ++++++++++++------------------- 3 files changed, 14 insertions(+), 26 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index 8a226758bc88..01dd149392f5 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -200,6 +200,8 @@ The following user-defined literals are defined in ``fmt/format.h``. Utilities --------- +.. doxygenclass:: fmt::is_char + .. doxygentypedef:: fmt::char_t .. doxygenfunction:: fmt::formatted_size(string_view, const Args&...) diff --git a/include/fmt/color.h b/include/fmt/color.h index bc534805bec1..f1683a7181f3 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -518,13 +518,6 @@ inline void reset_color(basic_memory_buffer& buffer) FMT_NOEXCEPT { buffer.append(begin, end); } -// The following specialization disables using std::FILE as a character type, -// which is needed because or else -// fmt::print(stderr, fmt::emphasis::bold, ""); -// would take stderr (a std::FILE *) as the format string. -template <> struct is_string : std::false_type {}; -template <> struct is_string : std::false_type {}; - template std::basic_string vformat(const text_style& ts, basic_string_view format_str, diff --git a/include/fmt/core.h b/include/fmt/core.h index 7e22c166558a..baee5ea96cef 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -442,33 +442,25 @@ template class basic_string_view { using string_view = basic_string_view; using wstring_view = basic_string_view; +/** Specifies if ``T`` is a character type. Can be specialized by users. */ +template struct is_char : std::is_integral {}; + /** \rst - The function ``to_string_view`` adapts non-intrusively any kind of string or - string-like type if the user provides a (possibly templated) overload of - ``to_string_view`` which takes an instance of the string class - ``StringType`` and returns a ``fmt::basic_string_view``. - The conversion function must live in the very same namespace as - ``StringType`` to be picked up by ADL. Non-templated string types - like f.e. QString must return a ``basic_string_view`` with a fixed matching - char type. + 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. **Example**:: namespace my_ns { - inline string_view to_string_view(const my_string &s) { + 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 -inline basic_string_view to_string_view(basic_string_view s) { - return s; -} - template inline basic_string_view to_string_view( const std::basic_string& s) { @@ -476,6 +468,11 @@ inline basic_string_view to_string_view( } template +inline basic_string_view to_string_view(basic_string_view s) { + return s; +} + +template ::value)> inline basic_string_view to_string_view(const Char* s) { return s; } @@ -597,10 +594,6 @@ struct is_string !std::is_same()))>::value> {}; -// Forward declare FILE* specialization defined in color.h -template <> struct is_string; -template <> struct is_string; - template struct char_t_impl { typedef decltype(to_string_view(std::declval())) result; typedef typename result::char_type type; From 1e6e87cb745e37719d49c0676a97a773e029e1ad Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 3 Jun 2019 11:35:12 -0700 Subject: [PATCH 03/11] Update docs --- doc/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.rst b/doc/api.rst index 01dd149392f5..49c6eea77676 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -210,7 +210,7 @@ Utilities .. doxygenfunction:: fmt::to_wstring(const T&) -.. doxygenfunction:: fmt::to_string_view(basic_string_view) +.. doxygenfunction:: fmt::to_string_view(const basic_string&) .. doxygenfunction:: fmt::join(const Range&, string_view) From 7e42c65bb68adb32158a4ac2255413aa232a4e53 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 3 Jun 2019 11:57:08 -0700 Subject: [PATCH 04/11] Document a more useful to_string_view overload --- doc/api.rst | 2 +- include/fmt/core.h | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index 49c6eea77676..c1c3ee2fe613 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -210,7 +210,7 @@ Utilities .. doxygenfunction:: fmt::to_wstring(const T&) -.. doxygenfunction:: fmt::to_string_view(const basic_string&) +.. doxygenfunction:: fmt::to_string_view(const Char*) .. doxygenfunction:: fmt::join(const Range&, string_view) diff --git a/include/fmt/core.h b/include/fmt/core.h index baee5ea96cef..f65d22f29ab3 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -461,6 +461,11 @@ template struct is_char : std::is_integral {}; std::string message = fmt::format(my_string("The answer is {}"), 42); \endrst */ +template ::value)> +inline basic_string_view to_string_view(const Char* s) { + return s; +} + template inline basic_string_view to_string_view( const std::basic_string& s) { @@ -472,11 +477,6 @@ inline basic_string_view to_string_view(basic_string_view s) { return s; } -template ::value)> -inline basic_string_view to_string_view(const Char* s) { - return s; -} - template >::value)> inline basic_string_view to_string_view( From eddb84cfc06751c5623a9fe3e56fac101e8c317c Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 3 Jun 2019 16:37:15 -0700 Subject: [PATCH 05/11] Fix formatting of exotic characters --- include/fmt/core.h | 2 +- include/fmt/format.h | 7 ++++--- test/format-test.cc | 18 ++++++++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index f65d22f29ab3..377e8127fc2a 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -771,7 +771,7 @@ FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) template ::value)> FMT_CONSTEXPR init make_value(Char val) { - return val; + return {val}; } template FMT_CONSTEXPR const Char* parse_precision(const Char* begin, const Char* end, Handler&& handler) { ++begin; - auto c = begin != end ? *begin : 0; + auto c = begin != end ? *begin : Char(); if ('0' <= c && c <= '9') { handler.on_precision(parse_nonnegative_int(begin, end, handler)); } else if (c == '{') { @@ -2763,8 +2763,9 @@ template class basic_writer { auto&& it = reserve(1); *it++ = value; } - void write(wchar_t value) { - static_assert(std::is_same::value, ""); + + template ::value)> + void write(Char value) { auto&& it = reserve(1); *it++ = value; } diff --git a/test/format-test.cc b/test/format-test.cc index 1c187651de4e..a0f877113abb 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2492,3 +2492,21 @@ TEST(FormatTest, CharTraitsIsNotAmbiguous) { (void)lval; #endif } + +struct mychar { + int value; + mychar() = default; + mychar(char val) : value(val) {} + operator int() const { return value; } +}; + +FMT_BEGIN_NAMESPACE +template <> struct is_char : std::true_type {}; +FMT_END_NAMESPACE + +TEST(FormatTest, FormatCustomChar) { + const mychar format[] = {'{', '}', 0}; + auto result = fmt::format(format, mychar('x')); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(result[0], mychar('x')); +} From 153024255169fbfb6d290ced4bbd83e0e6dbcd8e Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 3 Jun 2019 17:35:15 -0700 Subject: [PATCH 06/11] Fix signature in the docs --- doc/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.rst b/doc/api.rst index c1c3ee2fe613..c1eceaaf5a1e 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -210,7 +210,7 @@ Utilities .. doxygenfunction:: fmt::to_wstring(const T&) -.. doxygenfunction:: fmt::to_string_view(const Char*) +.. doxygenfunction:: fmt::to_string_view(const Char *) .. doxygenfunction:: fmt::join(const Range&, string_view) From 5bafcb437bd5cae86d53d4b97e2ef682a7e48819 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Mon, 3 Jun 2019 18:59:58 -0700 Subject: [PATCH 07/11] Add comments about things broken in MSVC --- include/fmt/core.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 377e8127fc2a..c7c74e221919 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -204,9 +204,9 @@ FMT_BEGIN_NAMESPACE template using enable_if_t = typename std::enable_if::type; -// enable_if helpers to be used in template parameters which results in much -// shorter symbols: https://godbolt.org/z/sWw4vP. -// Also include fix for VS2019 compilation issue (see #1140 and #1186). +// 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 namespace internal { @@ -587,6 +587,8 @@ dummy_string_view to_string_view(...); using fmt::v5::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. template struct is_string : std::integral_constant< From 7893d85394ea1cda51bc772eb5f7fc1b487f5b30 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 4 Jun 2019 08:47:25 -0700 Subject: [PATCH 08/11] Clarify why we cannot have nice things --- include/fmt/core.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/fmt/core.h b/include/fmt/core.h index c7c74e221919..780bb1385d22 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -1381,6 +1381,8 @@ struct is_contiguous_back_insert_iterator> : is_contiguous {}; /** Formats a string and writes the output to ``out``. */ +// GCC 8 and earlier cannot handle std::back_insert_iterator with +// vformat_to(...) overload, so SFINAE on iterator type instead. template , FMT_ENABLE_IF(is_contiguous_back_insert_iterator::value)> OutputIt vformat_to(OutputIt out, const S& format_str, From 064ce6b6c0b38ca1e54f621dc9e4d4bf3c4b8d5f Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 4 Jun 2019 17:08:58 -0700 Subject: [PATCH 09/11] Specialize is_char for character types --- include/fmt/core.h | 6 +++++- include/fmt/format.h | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/include/fmt/core.h b/include/fmt/core.h index 780bb1385d22..b684d6152a8e 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -443,7 +443,11 @@ using string_view = basic_string_view; using wstring_view = basic_string_view; /** Specifies if ``T`` is a character type. Can be specialized by users. */ -template struct is_char : std::is_integral {}; +template struct is_char : std::false_type {}; +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; +template <> struct is_char : std::true_type {}; /** \rst diff --git a/include/fmt/format.h b/include/fmt/format.h index 98d76d06d287..1507b4ea9541 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -400,6 +400,7 @@ void buffer::append(const U* begin, const U* end) { // A UTF-8 code unit type. enum char8_t : unsigned char {}; #endif +template <> struct is_char : std::true_type {}; // A UTF-8 string view. class u8string_view : public basic_string_view { From 4aa0dc578b237ecc4b70ff7ae6170a3989c038cb Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 4 Jun 2019 18:02:55 -0700 Subject: [PATCH 10/11] Fix docs --- doc/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/build.py b/doc/build.py index 0c856b20e1fe..5a4aa6520334 100755 --- a/doc/build.py +++ b/doc/build.py @@ -94,7 +94,7 @@ def build_docs(version='dev', **kwargs): "FMT_BEGIN_NAMESPACE=namespace fmt {{" \ "FMT_END_NAMESPACE=}}" \ "FMT_STRING_ALIAS=1" \ - "FMT_ENABLE_IF_T(B)=" + "FMT_ENABLE_IF(B)=" EXCLUDE_SYMBOLS = fmt::internal::* StringValue write_str '''.format(include_dir, doxyxml_dir).encode('UTF-8')) if p.returncode != 0: From c264e641eafcbec43b8bff4feea57428f1c38bb4 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Tue, 4 Jun 2019 18:50:30 -0700 Subject: [PATCH 11/11] Add conditional_t for pre-C++14 --- include/fmt/chrono.h | 6 +++--- include/fmt/core.h | 30 ++++++++++++++---------------- include/fmt/format.h | 11 ++++++----- include/fmt/prepare.h | 10 ++++------ include/fmt/printf.h | 9 ++++----- include/fmt/ranges.h | 10 +++++----- 6 files changed, 36 insertions(+), 40 deletions(-) diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 738f1beadd59..46efc511440b 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -450,9 +450,9 @@ struct chrono_formatter { OutputIt out; int precision; // rep is unsigned to avoid overflow. - using rep = typename std::conditional< - std::is_integral::value && sizeof(Rep) < sizeof(int), unsigned, - typename make_unsigned_or_unchanged::type>::type; + using rep = + conditional_t::value && sizeof(Rep) < sizeof(int), + unsigned, typename make_unsigned_or_unchanged::type>; rep val; typedef std::chrono::duration seconds; seconds s; diff --git a/include/fmt/core.h b/include/fmt/core.h index b684d6152a8e..c339d0b028b8 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -200,9 +200,11 @@ FMT_BEGIN_NAMESPACE -// An implementation of enable_if_t for pre-C++14 systems. +// Implementations of enable_if_t and other types for pre-C++14 systems. template using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; // 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 @@ -646,10 +648,9 @@ template struct string_value { }; template struct custom_value { + using parse_context = basic_parse_context; const void* value; - void (*format)(const void* arg, - basic_parse_context& parse_ctx, - Context& ctx); + void (*format)(const void* arg, parse_context& parse_ctx, Context& ctx); }; template @@ -704,10 +705,9 @@ template class value { // have different extension points, e.g. `formatter` for `format` and // `printf_formatter` for `printf`. custom.format = &format_custom_arg< - T, typename std::conditional< - is_formattable::value, - typename Context::template formatter_type, - internal::fallback_formatter>::type>; + T, conditional_t::value, + typename Context::template formatter_type, + internal::fallback_formatter>>; } const named_arg_base& as_named_arg() { @@ -758,12 +758,11 @@ FMT_MAKE_VALUE_SAME(uint_type, unsigned) // 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. -using long_type = - std::conditional::type; +using long_type = conditional_t; FMT_MAKE_VALUE((sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) -using ulong_type = std::conditional::type; +using ulong_type = conditional_t; FMT_MAKE_VALUE((sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), unsigned long, ulong_type) @@ -1129,9 +1128,8 @@ template class format_arg_store { // Packed is a macro on MinGW so use IS_PACKED instead. static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; - using value_type = - typename std::conditional, - basic_format_arg>::type; + using value_type = conditional_t, + basic_format_arg>; // If the arguments are not packed, add one more element to mark the end. static const size_t DATA_SIZE = @@ -1274,7 +1272,7 @@ template class basic_format_args { }; /** An alias to ``basic_format_args``. */ -// It is a separate type rather than a typedef to make symbols readable. +// It is a separate type rather than an alias to make symbols readable. struct format_args : basic_format_args { template format_args(Args&&... arg) diff --git a/include/fmt/format.h b/include/fmt/format.h index 1507b4ea9541..fa0a473aac3e 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -698,8 +698,8 @@ FMT_CONSTEXPR bool is_negative(T) { template struct int_traits { // Smallest of uint32_t and uint64_t that is large enough to represent // all values of T. - typedef typename std::conditional::digits <= 32, - uint32_t, uint64_t>::type main_type; + using main_type = + conditional_t::digits <= 32, uint32_t, uint64_t>; }; // Static data is placed in this class template to allow header-only @@ -2181,9 +2181,10 @@ FMT_CONSTEXPR const typename ParseContext::char_type* parse_format_specs( ParseContext& ctx) { // GCC 7.2 requires initializer. typedef typename ParseContext::char_type char_type; - typename std::conditional::value, - formatter, - internal::fallback_formatter>::type f; + conditional_t::value, + formatter, + internal::fallback_formatter> + f; return f.parse(ctx); } diff --git a/include/fmt/prepare.h b/include/fmt/prepare.h index 9b30b5d6b760..a7078ef80294 100644 --- a/include/fmt/prepare.h +++ b/include/fmt/prepare.h @@ -440,9 +440,8 @@ template class compiletime_prepared_parts_type_provider { typedef format_part value_type; }; - typedef typename std::conditional(number_of_format_parts), - format_parts_array, - empty>::type type; + using type = conditional_t(number_of_format_parts), + format_parts_array, empty>; }; template class compiletime_prepared_parts_collector { @@ -674,9 +673,8 @@ struct compiletime_format_tag {}; struct runtime_format_tag {}; template struct format_tag { - typedef typename std::conditional::value, - compiletime_format_tag, - runtime_format_tag>::type type; + using type = conditional_t::value, + compiletime_format_tag, runtime_format_tag>; }; #if FMT_USE_CONSTEXPR diff --git a/include/fmt/printf.h b/include/fmt/printf.h index 29f36323a55c..0828c321f21b 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -91,15 +91,14 @@ class arg_converter : public function { template ::value)> void operator()(U value) { bool is_signed = type_ == 'd' || type_ == 'i'; - typedef typename std::conditional::value, U, T>::type - TargetType; - if (const_check(sizeof(TargetType) <= sizeof(int))) { + using target_type = conditional_t::value, U, T>; + if (const_check(sizeof(target_type) <= sizeof(int))) { // Extra casts are used to silence warnings. if (is_signed) { arg_ = internal::make_arg( - static_cast(static_cast(value))); + static_cast(static_cast(value))); } else { - typedef typename make_unsigned_or_bool::type Unsigned; + typedef typename make_unsigned_or_bool::type Unsigned; arg_ = internal::make_arg( static_cast(static_cast(value))); } diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index a3a0c7a03c88..18124d3a4fa0 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -94,11 +94,11 @@ template struct is_range_ : std::false_type {}; #if !FMT_MSC_VER || FMT_MSC_VER > 1800 template -struct is_range_().begin()), - decltype(std::declval().end())>, - void>::type> : std::true_type {}; +struct is_range_< + T, conditional_t().begin()), + decltype(std::declval().end())>, + void>> : std::true_type {}; #endif /// tuple_size and tuple_element check.