diff --git a/include/fmt/format.h b/include/fmt/format.h index 9448628d..055e959a 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -176,6 +176,10 @@ FMT_END_NAMESPACE # define FMT_USE_TRAILING_RETURN 0 #endif +#ifndef FMT_USE_INT128 +# define FMT_USE_INT128 (__SIZEOF_INT128__ != 0) +#endif + // __builtin_clz is broken in clang with Microsoft CodeGen: // https://github.com/fmtlib/fmt/issues/519 #ifndef _MSC_VER @@ -1418,7 +1422,8 @@ void arg_map::init(const basic_format_args& args) { } } -template class arg_formatter_base { +template +class arg_formatter_base { public: typedef typename Range::value_type char_type; typedef decltype(internal::declval().begin()) iterator; @@ -1498,7 +1503,7 @@ template class arg_formatter_base { return out(); } - struct char_spec_handler : internal::error_handler { + struct char_spec_handler : ErrorHandler { arg_formatter_base& formatter; char_type value; @@ -2732,7 +2737,8 @@ template class basic_writer { } }; - template friend class internal::arg_formatter_base; + template + friend class internal::arg_formatter_base; public: /** Constructs a ``basic_writer`` object. */ diff --git a/include/format b/include/format index 2427e79d..66a1e440 100644 --- a/include/format +++ b/include/format @@ -17,9 +17,6 @@ // std::variant and should store packed argument type tags separately from // values in basic_format_args for small number of arguments. -#define FMT_REQUIRES(...) -#define FMT_CONCEPT(C) typename - namespace std { template constexpr bool Integral = is_integral_v; @@ -117,6 +114,17 @@ namespace std { }; } +namespace std { +namespace detail { +struct error_handler { + // This function is intentionally not constexpr to give a compile-time error. + void on_error(const char* message) { + throw std::format_error(message); + } +}; +} +} + // http://fmtlib.net/Text%20Formatting.html#format.parse_context namespace std { template @@ -149,7 +157,7 @@ namespace std { // Implementation detail: constexpr void check_arg_id(fmt::string_view) {} - fmt::internal::error_handler error_handler() const { return {}; } + detail::error_handler error_handler() const { return {}; } void on_error(const char* msg) { error_handler().on_error(msg); } }; } @@ -212,7 +220,7 @@ namespace std { using format_arg = basic_format_arg; basic_format_context(Out out, basic_format_args args, fmt::internal::locale_ref) : args_(args), out_(out) {} - fmt::internal::error_handler error_handler() const { return {}; } + detail::error_handler error_handler() const { return {}; } basic_format_arg arg(fmt::basic_string_view) const { return {}; // unused: named arguments are not supported yet } @@ -482,11 +490,11 @@ namespace detail { template class arg_formatter : public fmt::internal::function< - typename fmt::internal::arg_formatter_base::iterator>, - public fmt::internal::arg_formatter_base { + typename fmt::internal::arg_formatter_base::iterator>, + public fmt::internal::arg_formatter_base { private: using char_type = typename Range::value_type; - using base = fmt::internal::arg_formatter_base; + using base = fmt::internal::arg_formatter_base; using format_context = std::basic_format_context; using parse_context = basic_format_parse_context; @@ -574,7 +582,7 @@ class custom_formatter { }; template -struct format_handler : fmt::internal::error_handler { +struct format_handler : detail::error_handler { typedef typename ArgFormatter::range range; format_handler(range r, basic_string_view str, @@ -854,50 +862,4 @@ template <> struct formatter : detail::formatter6}", 'x'); // s3 == "*****x" - string s4 = format("{:*^6}", 'x'); // s4 == "**x***" - string s5 = format("{:=6}", 'x'); // Error: '=' with charT and no integer presentation type - string s6 = format("{:6d}", c); // s6 == " 120" - string s7 = format("{:=+06d}", c); // s7 == "+00120" - string s8 = format("{:0=#6x}", 0xa); // s8 == "0x000a" - string s9 = format("{:6}", true); // s9 == "true " -} - -inline void test3() { - using namespace std; - double inf = numeric_limits::infinity(); - double nan = numeric_limits::quiet_NaN(); - string s0 = format("{0:} {0:+} {0:-} {0: }", 1); // s0 == "1 +1 1 1" - string s1 = format("{0:} {0:+} {0:-} {0: }", -1); // s1 == "-1 -1 -1 -1" - string s2 = format("{0:} {0:+} {0:-} {0: }", inf); // s2 == "inf +inf inf inf" - string s3 = format("{0:} {0:+} {0:-} {0: }", nan); // s3 == "nan +nan nan nan" -} - -inline void test4() { - using namespace std; - string s0 = format("{}", 42); // s0 == "42" - string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a" - string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A" - string s3 = format("{:n}", 1234); // s3 == "1,234" (depends on the locale) -} - #endif // FMT_FORMAT_ diff --git a/support/cmake/cxx14.cmake b/support/cmake/cxx14.cmake index de8a0b42..032fcb27 100644 --- a/support/cmake/cxx14.cmake +++ b/support/cmake/cxx14.cmake @@ -68,4 +68,14 @@ if (NOT SUPPORTS_USER_DEFINED_LITERALS) set (SUPPORTS_USER_DEFINED_LITERALS OFF) endif () +# Check if is available +set(CMAKE_REQUIRED_FLAGS -std=c++1z) +check_cxx_source_compiles(" + #include + int main() {}" + FMT_HAS_VARIANT) +if (NOT FMT_HAS_VARIANT) + set (FMT_HAS_VARIANT OFF) +endif () + set(CMAKE_REQUIRED_FLAGS ) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6940c1bb..e3667d06 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -33,7 +33,7 @@ endif () # Silence MSVC tr1 deprecation warning in gmock. target_compile_definitions(gmock - PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=0) + PUBLIC _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING=1) #------------------------------------------------------------------------------ # Build the actual library tests @@ -99,6 +99,12 @@ add_fmt_test(printf-test) add_fmt_test(custom-formatter-test) add_fmt_test(ranges-test) +# MSVC fails to compile GMock when C++17 is enabled. +if (FMT_HAS_VARIANT AND NOT MSVC) + add_fmt_test(std-format-test) + set_property(TARGET std-format-test PROPERTY CXX_STANDARD 17) +endif () + if (HAVE_OPEN) add_fmt_executable(posix-mock-test posix-mock-test.cc ../src/format.cc ${TEST_MAIN_SRC}) diff --git a/test/std-format-test.cc b/test/std-format-test.cc new file mode 100644 index 00000000..7fbd4607 --- /dev/null +++ b/test/std-format-test.cc @@ -0,0 +1,151 @@ +#include +#include "gtest.h" + +TEST(StdFormatTest, Escaping) { + using namespace std; + string s = format("{0}-{{", 8); // s == "8-{" + EXPECT_EQ(s, "8-{"); +} + +TEST(StdFormatTest, Indexing) { + using namespace std; + string s0 = format("{} to {}", "a", "b"); // OK: automatic indexing + string s1 = format("{1} to {0}", "a", "b"); // OK: manual indexing + EXPECT_EQ(s0, "a to b"); + EXPECT_EQ(s1, "b to a"); + // Error: mixing automatic and manual indexing + EXPECT_THROW(string s2 = format("{0} to {}", "a", "b"), std::format_error); + // Error: mixing automatic and manual indexing + EXPECT_THROW(string s3 = format("{} to {1}", "a", "b"), std::format_error); +} + +TEST(StdFormatTest, Alignment) { + using namespace std; + char c = 120; + string s0 = format("{:6}", 42); // s0 == " 42" + string s1 = format("{:6}", 'x'); // s1 == "x " + string s2 = format("{:*<6}", 'x'); // s2 == "x*****" + string s3 = format("{:*>6}", 'x'); // s3 == "*****x" + string s4 = format("{:*^6}", 'x'); // s4 == "**x***" + // Error: '=' with charT and no integer presentation type + EXPECT_THROW(string s5 = format("{:=6}", 'x'), std::format_error); + string s6 = format("{:6d}", c); // s6 == " 120" + string s7 = format("{:=+06d}", c); // s7 == "+00120" + string s8 = format("{:0=#6x}", 0xa); // s8 == "0x000a" + string s9 = format("{:6}", true); // s9 == "true " + EXPECT_EQ(s0, " 42"); + EXPECT_EQ(s1, "x "); + EXPECT_EQ(s2, "x*****"); + EXPECT_EQ(s3, "*****x"); + EXPECT_EQ(s4, "**x***"); + EXPECT_EQ(s6, " 120"); + EXPECT_EQ(s7, "+00120"); + EXPECT_EQ(s8, "0x000a"); + EXPECT_EQ(s9, "true "); +} + +TEST(StdFormatTest, Float) { + using namespace std; + double inf = numeric_limits::infinity(); + double nan = numeric_limits::quiet_NaN(); + string s0 = format("{0:} {0:+} {0:-} {0: }", 1); // s0 == "1 +1 1 1" + string s1 = format("{0:} {0:+} {0:-} {0: }", -1); // s1 == "-1 -1 -1 -1" + string s2 = format("{0:} {0:+} {0:-} {0: }", inf); // s2 == "inf +inf inf inf" + string s3 = format("{0:} {0:+} {0:-} {0: }", nan); // s3 == "nan +nan nan nan" + EXPECT_EQ(s0, "1 +1 1 1"); + EXPECT_EQ(s1, "-1 -1 -1 -1"); + EXPECT_EQ(s2, "inf +inf inf inf"); + EXPECT_EQ(s3, "nan +nan nan nan"); +} + +TEST(StdFormatTest, Int) { + using namespace std; + string s0 = format("{}", 42); // s0 == "42" + string s1 = format("{0:b} {0:d} {0:o} {0:x}", 42); // s1 == "101010 42 52 2a" + string s2 = format("{0:#x} {0:#X}", 42); // s2 == "0x2a 0X2A" + string s3 = format("{:n}", 1234); // s3 == "1,234" (depends on the locale) + EXPECT_EQ(s0, "42"); + EXPECT_EQ(s1, "101010 42 52 2a"); + EXPECT_EQ(s2, "0x2a 0X2A"); + EXPECT_EQ(s3, "1,234"); +} + +#include + +enum color { red, green, blue }; + +const char* color_names[] = { "red", "green", "blue" }; + +template<> struct std::formatter : std::formatter { + auto format(color c, format_context& ctx) { + return formatter::format(color_names[c], ctx); + } +}; + +struct err {}; + +TEST(StdFormatTest, Formatter) { + std::string s0 = std::format("{}", 42); // OK: library-provided formatter + //std::string s1 = std::format("{}", L"foo"); // Ill-formed: disabled formatter + std::string s2 = std::format("{}", red); // OK: user-provided formatter + //std::string s3 = std::format("{}", err{}); // Ill-formed: disabled formatter + EXPECT_EQ(s0, "42"); + EXPECT_EQ(s2, "red"); +} + +struct S { + int value; +}; + +template<> struct std::formatter { + size_t width_arg_id = 0; + + // Parses a width argument id in the format { }. + constexpr auto parse(format_parse_context& ctx) { + auto iter = ctx.begin(); + auto get_char = [&]() { return iter != ctx.end() ? *iter : 0; }; + if (get_char() != '{') + return iter; + ++iter; + char c = get_char(); + if (!isdigit(c) || (++iter, get_char()) != '}') + throw format_error("invalid format"); + width_arg_id = c - '0'; + ctx.check_arg_id(width_arg_id); + return ++iter; + } + + // Formats S with width given by the argument width_arg_id. + auto format(S s, format_context& ctx) { + int width = visit_format_arg([](auto value) -> int { + if constexpr (!is_integral_v) + throw format_error("width is not integral"); + else if (value < 0 || value > numeric_limits::max()) + throw format_error("invalid width"); + else + return value; + }, ctx.arg(width_arg_id)); + return format_to(ctx.out(), "{0:{1}}", s.value, width); + } +}; + +TEST(StdFormatTest, Parsing) { + std::string s = std::format("{0:{1}}", S{42}, 10); // s == " 42" + EXPECT_EQ(s, " 42"); +} + +#if FMT_USE_INT128 +template <> struct std::formatter<__int128_t> : std::formatter { + auto format(__int128_t n, format_context& ctx) { + // Format as a long long since we only want to check if it is possible to + // specialize formatter for __int128_t. + return formatter::format(n, ctx); + } +}; + +TEST(StdFormatTest, Int128) { + __int128_t n = 42; + auto s = std::format("{}", n); + EXPECT_EQ(s, "42"); +} +#endif // FMT_USE_INT128