diff --git a/include/fmt/format.h b/include/fmt/format.h index cdb9f91a..3fcf3b20 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -2803,24 +2803,6 @@ constexpr auto operator"" _format(const char* s, size_t n) } } // namespace literals -template ::value)> -auto vformat(basic_string_view format_str, - basic_format_args>> args) - -> std::basic_string { - basic_memory_buffer buffer; - detail::vformat_to(buffer, format_str, args); - return to_string(buffer); -} - -// Pass char_t as a default template parameter instead of using -// std::basic_string> to reduce the symbol size. -template , - FMT_ENABLE_IF(!std::is_same::value)> -auto format(const S& format_str, Args&&... args) -> std::basic_string { - const auto& vargs = fmt::make_args_checked(format_str, args...); - return vformat(to_string_view(format_str), vargs); -} - template ::value)> inline auto vformat(const Locale& loc, string_view fmt, format_args args) -> std::string { diff --git a/include/fmt/xchar.h b/include/fmt/xchar.h index f316ad17..69f44864 100644 --- a/include/fmt/xchar.h +++ b/include/fmt/xchar.h @@ -73,6 +73,24 @@ auto join(std::initializer_list list, wstring_view sep) return join(std::begin(list), std::end(list), sep); } +template ::value)> +auto vformat(basic_string_view format_str, + basic_format_args>> args) + -> std::basic_string { + basic_memory_buffer buffer; + detail::vformat_to(buffer, format_str, args); + return to_string(buffer); +} + +// Pass char_t as a default template parameter instead of using +// std::basic_string> to reduce the symbol size. +template , + FMT_ENABLE_IF(!std::is_same::value)> +auto format(const S& format_str, Args&&... args) -> std::basic_string { + const auto& vargs = fmt::make_args_checked(format_str, args...); + return vformat(to_string_view(format_str), vargs); +} + template , FMT_ENABLE_IF(detail::is_locale::value&& detail::is_exotic_char::value)> diff --git a/test/chrono-test.cc b/test/chrono-test.cc index 98e603ab..6bc2cdb0 100644 --- a/test/chrono-test.cc +++ b/test/chrono-test.cc @@ -48,8 +48,6 @@ TEST(chrono_test, format_tm) { tm.tm_sec = 33; EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), "The date is 2016-04-25 11:22:33."); - EXPECT_EQ(fmt::format(L"The date is {:%Y-%m-%d %H:%M:%S}.", tm), - L"The date is 2016-04-25 11:22:33."); } TEST(chrono_test, grow_buffer) { @@ -151,10 +149,6 @@ TEST(chrono_test, format_default) { fmt::format("{}", std::chrono::duration>(42))); } -TEST(chrono_test, format_wide) { - EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); -} - TEST(chrono_test, align) { auto s = std::chrono::seconds(42); EXPECT_EQ("42s ", fmt::format("{:5}", s)); diff --git a/test/format-test.cc b/test/format-test.cc index 84d503ec..13e2ffc1 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -634,13 +634,6 @@ TEST(format_test, space_sign) { format_error, "format specifier requires numeric argument"); } -TEST(format_test, sign_not_truncated) { - wchar_t format_str[] = { - L'{', L':', - '+' | static_cast(1 << fmt::detail::num_bits()), L'}', 0}; - EXPECT_THROW(fmt::format(format_str, 42), format_error); -} - TEST(format_test, hash_flag) { EXPECT_EQ("42", fmt::format("{0:#}", 42)); EXPECT_EQ("-42", fmt::format("{0:#}", -42)); @@ -1019,7 +1012,6 @@ TEST(format_test, format_bool) { EXPECT_EQ("false", fmt::format("{}", false)); EXPECT_EQ("1", fmt::format("{:d}", true)); EXPECT_EQ("true ", fmt::format("{:5}", true)); - EXPECT_EQ(L"true", fmt::format(L"{}", true)); EXPECT_EQ("true", fmt::format("{:s}", true)); EXPECT_EQ("false", fmt::format("{:s}", false)); EXPECT_EQ("false ", fmt::format("{:6s}", false)); @@ -1330,7 +1322,6 @@ TEST(format_test, format_char) { check_unknown_types('a', types, "char"); EXPECT_EQ("a", fmt::format("{0}", 'a')); EXPECT_EQ("z", fmt::format("{0:c}", 'z')); - EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); int n = 'x'; for (const char* type = types + 1; *type; ++type) { std::string format_str = fmt::format("{{:{}}}", *type); @@ -1351,12 +1342,6 @@ TEST(format_test, format_unsigned_char) { EXPECT_EQ("42", fmt::format("{}", static_cast(42))); } -TEST(format_test, format_wchar) { - EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); - // This shouldn't compile: - // format("{}", L'a'); -} - TEST(format_test, format_cstring) { check_unknown_types("test", "sp", "string"); EXPECT_EQ("test", fmt::format("{0}", "test")); @@ -1451,28 +1436,6 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) { } #endif -namespace fake_qt { -class QString { - public: - QString(const wchar_t* s) : s_(s) {} - const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); } - int size() const FMT_NOEXCEPT { return static_cast(s_.size()); } - - private: - std::wstring s_; -}; - -fmt::basic_string_view to_string_view(const QString& s) FMT_NOEXCEPT { - return {s.utf16(), static_cast(s.size())}; -} -} // namespace fake_qt - -TEST(format_test, format_foreign_strings) { - using fake_qt::QString; - EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42"); - EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42"); -} - class Answer {}; FMT_BEGIN_NAMESPACE @@ -1513,14 +1476,6 @@ TEST(format_test, format_to_custom) { EXPECT_STREQ(buf, "42"); } -TEST(format_test, wide_format_string) { - EXPECT_EQ(L"42", fmt::format(L"{}", 42)); - EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); - EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); - EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); - EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error); -} - TEST(format_test, format_string_from_speed_test) { EXPECT_EQ("1.2340000000:0042:+3.13:str:0x3e8:X:%", fmt::format("{0:0.10f}:{1:04}:{2:+g}:{3}:{4}:{5}:%", 1.234, 42, @@ -1587,9 +1542,6 @@ TEST(format_test, format_examples) { EXPECT_THROW_MSG(fmt::format(runtime("The answer is {:d}"), "forty-two"), format_error, "invalid type specifier"); - EXPECT_EQ(L"Cyrillic letter \x42e", - fmt::format(L"Cyrillic letter {}", L'\x42e')); - EXPECT_WRITE( stdout, fmt::print("{}", std::numeric_limits::infinity()), "inf"); } @@ -1602,7 +1554,6 @@ TEST(format_test, print) { TEST(format_test, variadic) { EXPECT_EQ("abc1", fmt::format("{}c{}", "ab", 1)); - EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); } TEST(format_test, dynamic) { @@ -1698,15 +1649,11 @@ fmt::string_view to_string_view(string_like) { return "foo"; } constexpr char with_null[3] = {'{', '}', '\0'}; constexpr char no_null[2] = {'{', '}'}; static FMT_CONSTEXPR_DECL const char static_with_null[3] = {'{', '}', '\0'}; -static FMT_CONSTEXPR_DECL const wchar_t static_with_null_wide[3] = {'{', '}', - '\0'}; static FMT_CONSTEXPR_DECL const char static_no_null[2] = {'{', '}'}; -static FMT_CONSTEXPR_DECL const wchar_t static_no_null_wide[2] = {'{', '}'}; TEST(format_test, compile_time_string) { EXPECT_EQ("foo", fmt::format(FMT_STRING("foo"))); EXPECT_EQ("42", fmt::format(FMT_STRING("{}"), 42)); - EXPECT_EQ(L"42", fmt::format(FMT_STRING(L"{}"), 42)); EXPECT_EQ("foo", fmt::format(FMT_STRING("{}"), string_like())); #if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS @@ -1718,14 +1665,10 @@ TEST(format_test, compile_time_string) { #endif (void)static_with_null; - (void)static_with_null_wide; (void)static_no_null; - (void)static_no_null_wide; #ifndef _MSC_VER EXPECT_EQ("42", fmt::format(FMT_STRING(static_with_null), 42)); - EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_with_null_wide), 42)); EXPECT_EQ("42", fmt::format(FMT_STRING(static_no_null), 42)); - EXPECT_EQ(L"42", fmt::format(FMT_STRING(static_no_null_wide), 42)); #endif (void)with_null; @@ -1736,7 +1679,6 @@ TEST(format_test, compile_time_string) { #endif #if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L EXPECT_EQ("42", fmt::format(FMT_STRING(std::string_view("{}")), 42)); - EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); #endif } @@ -2119,49 +2061,6 @@ TEST(format_test, char_traits_is_not_ambiguous) { #endif } -#if __cplusplus > 201103L -struct custom_char { - int value; - custom_char() = default; - - template - constexpr custom_char(T val) : value(static_cast(val)) {} - - operator int() const { return value; } -}; - -int to_ascii(custom_char c) { return c; } - -FMT_BEGIN_NAMESPACE -template <> struct is_char : std::true_type {}; -FMT_END_NAMESPACE - -TEST(format_test, format_custom_char) { - const custom_char format[] = {'{', '}', 0}; - auto result = fmt::format(format, custom_char('x')); - EXPECT_EQ(result.size(), 1); - EXPECT_EQ(result[0], custom_char('x')); -} -#endif - -// Convert a char8_t string to std::string. Otherwise GTest will insist on -// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423. -template std::string from_u8str(const S& str) { - return std::string(str.begin(), str.end()); -} - -TEST(format_test, format_utf8_precision) { - using str_type = std::basic_string; - auto format = - str_type(reinterpret_cast(u8"{:.4}")); - auto str = str_type(reinterpret_cast( - u8"caf\u00e9s")); // cafés - auto result = fmt::format(format, str); - EXPECT_EQ(fmt::detail::compute_width(result), 4); - EXPECT_EQ(result.size(), 5); - EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5))); -} - struct check_back_appender {}; FMT_BEGIN_NAMESPACE diff --git a/test/os-test.cc b/test/os-test.cc index cc23e6b8..a34f96e6 100644 --- a/test/os-test.cc +++ b/test/os-test.cc @@ -80,18 +80,6 @@ TEST(os_test, format_std_error_code) { std::error_code(-42, fmt::system_category()))); } -TEST(os_test, format_std_error_code_wide) { - EXPECT_EQ(L"generic:42", - fmt::format(FMT_STRING(L"{0}"), - std::error_code(42, std::generic_category()))); - EXPECT_EQ(L"system:42", - fmt::format(FMT_STRING(L"{0}"), - std::error_code(42, fmt::system_category()))); - EXPECT_EQ(L"system:-42", - fmt::format(FMT_STRING(L"{0}"), - std::error_code(-42, fmt::system_category()))); -} - TEST(os_test, format_windows_error) { LPWSTR message = 0; auto result = FormatMessageW( diff --git a/test/ostream-test.cc b/test/ostream-test.cc index 99bc6e5b..62e45f5d 100644 --- a/test/ostream-test.cc +++ b/test/ostream-test.cc @@ -45,29 +45,22 @@ template void operator,(type_with_comma_op, const T&); template type_with_comma_op operator<<(T&, const date&); enum streamable_enum {}; + std::ostream& operator<<(std::ostream& os, streamable_enum) { return os << "streamable_enum"; } -std::wostream& operator<<(std::wostream& os, streamable_enum) { - return os << L"streamable_enum"; -} - enum unstreamable_enum {}; TEST(ostream_test, enum) { EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum())); EXPECT_EQ("0", fmt::format("{}", unstreamable_enum())); - EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum())); - EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum())); } TEST(ostream_test, format) { EXPECT_EQ("a string", fmt::format("{0}", test_string("a string"))); EXPECT_EQ("The date is 2012-12-9", fmt::format("The date is {0}", date(2012, 12, 9))); - EXPECT_EQ(L"The date is 2012-12-9", - fmt::format(L"The date is {0}", date(2012, 12, 9))); } TEST(ostream_test, format_specs) { @@ -220,7 +213,6 @@ template struct convertible { TEST(ostream_test, disable_builtin_ostream_operators) { EXPECT_EQ("42", fmt::format("{:d}", convertible(42))); - EXPECT_EQ(L"42", fmt::format(L"{:d}", convertible(42))); EXPECT_EQ("foo", fmt::format("{}", convertible("foo"))); } diff --git a/test/printf-test.cc b/test/printf-test.cc index 5aaa306e..e1b606a1 100644 --- a/test/printf-test.cc +++ b/test/printf-test.cc @@ -11,8 +11,8 @@ #include #include -#include "fmt/core.h" #include "fmt/ostream.h" +#include "fmt/xchar.h" #include "gtest-extra.h" #include "util.h" diff --git a/test/ranges-test.cc b/test/ranges-test.cc index 64f65e46..4932fe21 100644 --- a/test/ranges-test.cc +++ b/test/ranges-test.cc @@ -219,11 +219,6 @@ TEST(ranges_test, join_tuple) { EXPECT_EQ(fmt::format("{}", fmt::join(t4, "/")), "4"); } -TEST(ranges_test, wide_string_join_tuple) { - auto t = std::tuple('a', 1, 2.0f); - EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)"); -} - TEST(ranges_test, join_initializer_list) { EXPECT_EQ(fmt::format("{}", fmt::join({1, 2, 3}, ", ")), "1, 2, 3"); EXPECT_EQ(fmt::format("{}", fmt::join({"fmt", "rocks", "!"}, " ")), diff --git a/test/wchar-test.cc b/test/wchar-test.cc index 4569a11a..bf512629 100644 --- a/test/wchar-test.cc +++ b/test/wchar-test.cc @@ -7,6 +7,9 @@ #include +#include "fmt/chrono.h" +#include "fmt/ostream.h" +#include "fmt/ranges.h" #include "fmt/xchar.h" #include "gtest/gtest.h" @@ -24,6 +27,69 @@ TEST(wchar_test, format_explicitly_convertible_to_wstring_view) { } #endif +TEST(wchar_test, format) { + EXPECT_EQ(L"42", fmt::format(L"{}", 42)); + EXPECT_EQ(L"4.2", fmt::format(L"{}", 4.2)); + EXPECT_EQ(L"abc", fmt::format(L"{}", L"abc")); + EXPECT_EQ(L"z", fmt::format(L"{}", L'z')); + EXPECT_THROW(fmt::format(L"{:*\x343E}", 42), fmt::format_error); + EXPECT_EQ(L"true", fmt::format(L"{}", true)); + EXPECT_EQ(L"a", fmt::format(L"{0}", 'a')); + EXPECT_EQ(L"a", fmt::format(L"{0}", L'a')); + EXPECT_EQ(L"Cyrillic letter \x42e", + fmt::format(L"Cyrillic letter {}", L'\x42e')); + EXPECT_EQ(L"abc1", fmt::format(L"{}c{}", L"ab", 1)); +} + +TEST(wchar_test, compile_time_string) { +#if defined(FMT_USE_STRING_VIEW) && __cplusplus >= 201703L + EXPECT_EQ(L"42", fmt::format(FMT_STRING(std::wstring_view(L"{}")), 42)); +#endif +} + +#if __cplusplus > 201103L +struct custom_char { + int value; + custom_char() = default; + + template + constexpr custom_char(T val) : value(static_cast(val)) {} + + operator int() const { return value; } +}; + +int to_ascii(custom_char c) { return c; } + +FMT_BEGIN_NAMESPACE +template <> struct is_char : std::true_type {}; +FMT_END_NAMESPACE + +TEST(wchar_test, format_custom_char) { + const custom_char format[] = {'{', '}', 0}; + auto result = fmt::format(format, custom_char('x')); + EXPECT_EQ(result.size(), 1); + EXPECT_EQ(result[0], custom_char('x')); +} +#endif + +// Convert a char8_t string to std::string. Otherwise GTest will insist on +// inserting `char8_t` NTBS into a `char` stream which is disabled by P1423. +template std::string from_u8str(const S& str) { + return std::string(str.begin(), str.end()); +} + +TEST(wchar_test, format_utf8_precision) { + using str_type = std::basic_string; + auto format = + str_type(reinterpret_cast(u8"{:.4}")); + auto str = str_type(reinterpret_cast( + u8"caf\u00e9s")); // cafés + auto result = fmt::format(format, str); + EXPECT_EQ(fmt::detail::compute_width(result), 4); + EXPECT_EQ(result.size(), 5); + EXPECT_EQ(from_u8str(result), from_u8str(str.substr(0, 5))); +} + TEST(wchar_test, format_to) { auto buf = std::vector(); fmt::format_to(std::back_inserter(buf), L"{}{}", 42, L'\0'); @@ -88,6 +154,63 @@ TEST(wchar_test, print) { TEST(wchar_test, join) { int v[3] = {1, 2, 3}; EXPECT_EQ(fmt::format(L"({})", fmt::join(v, v + 3, L", ")), L"(1, 2, 3)"); + auto t = std::tuple('a', 1, 2.0f); + EXPECT_EQ(fmt::format(L"({})", fmt::join(t, L", ")), L"(a, 1, 2)"); +} + +enum streamable_enum {}; + +std::wostream& operator<<(std::wostream& os, streamable_enum) { + return os << L"streamable_enum"; +} + +enum unstreamable_enum {}; + +TEST(wchar_test, enum) { + EXPECT_EQ(L"streamable_enum", fmt::format(L"{}", streamable_enum())); + EXPECT_EQ(L"0", fmt::format(L"{}", unstreamable_enum())); +} + +TEST(wchar_test, sign_not_truncated) { + wchar_t format_str[] = { + L'{', L':', + '+' | static_cast(1 << fmt::detail::num_bits()), L'}', 0}; + EXPECT_THROW(fmt::format(format_str, 42), fmt::format_error); +} + +namespace fake_qt { +class QString { + public: + QString(const wchar_t* s) : s_(s) {} + const wchar_t* utf16() const FMT_NOEXCEPT { return s_.data(); } + int size() const FMT_NOEXCEPT { return static_cast(s_.size()); } + + private: + std::wstring s_; +}; + +fmt::basic_string_view to_string_view(const QString& s) FMT_NOEXCEPT { + return {s.utf16(), static_cast(s.size())}; +} +} // namespace fake_qt + +TEST(format_test, format_foreign_strings) { + using fake_qt::QString; + EXPECT_EQ(fmt::format(QString(L"{}"), 42), L"42"); + EXPECT_EQ(fmt::format(QString(L"{}"), QString(L"42")), L"42"); +} + +TEST(wchar_test, chrono) { + auto tm = std::tm(); + tm.tm_year = 116; + tm.tm_mon = 3; + tm.tm_mday = 25; + tm.tm_hour = 11; + tm.tm_min = 22; + tm.tm_sec = 33; + EXPECT_EQ(fmt::format("The date is {:%Y-%m-%d %H:%M:%S}.", tm), + "The date is 2016-04-25 11:22:33."); + EXPECT_EQ(L"42s", fmt::format(L"{}", std::chrono::seconds(42))); } TEST(wchar_test, to_wstring) { EXPECT_EQ(L"42", fmt::to_wstring(42)); }