diff --git a/doc/api.rst b/doc/api.rst index 8a226758bc88..c1eceaaf5a1e 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&...) @@ -208,7 +210,7 @@ Utilities .. doxygenfunction:: fmt::to_wstring(const T&) -.. doxygenfunction:: fmt::to_string_view(basic_string_view) +.. doxygenfunction:: fmt::to_string_view(const Char *) .. doxygenfunction:: fmt::join(const Range&, string_view) 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: diff --git a/include/fmt/chrono.h b/include/fmt/chrono.h index 48368408224e..f2c7e8081274 100644 --- a/include/fmt/chrono.h +++ b/include/fmt/chrono.h @@ -496,9 +496,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/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 58c716d85f15..c339d0b028b8 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -200,13 +200,16 @@ 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. -#define FMT_ENABLE_IF(...) enable_if_t<__VA_ARGS__, int> = 0 +// 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 { @@ -441,30 +444,31 @@ 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::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 - 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) { +template ::value)> +inline basic_string_view to_string_view(const Char* s) { return s; } @@ -475,7 +479,7 @@ inline basic_string_view to_string_view( } template -inline basic_string_view to_string_view(const Char* s) { +inline basic_string_view to_string_view(basic_string_view s) { return s; } @@ -589,6 +593,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< @@ -596,10 +602,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; @@ -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) @@ -777,7 +776,7 @@ FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) template ::value)> FMT_CONSTEXPR init make_value(Char val) { - return val; + return {val}; } 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) @@ -1385,6 +1383,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, diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index c15cbc74b61f..5d7696065409 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 95332419b962..b60bb95f0299 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 { @@ -702,8 +703,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 @@ -2036,7 +2037,7 @@ 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 == '{') { @@ -2191,9 +2192,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); } @@ -2257,7 +2259,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 = @@ -2774,8 +2776,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/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. 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')); +}