diff --git a/include/fmt/compile.h b/include/fmt/compile.h index f5f08602..ffc8c91f 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -408,6 +408,23 @@ template struct field { template struct is_compiled_format> : std::true_type {}; +// A replacement field that refers to argument N and has format specifiers. +template struct spec_field { + using char_type = Char; + mutable formatter fmt; + + template + OutputIt format(OutputIt out, const Args&... args) const { + // This ensures that the argument type is convertile to `const T&`. + const T& arg = get(args...); + basic_format_context ctx(out, {}); + return fmt.format(arg, ctx); + } +}; + +template +struct is_compiled_format> : std::true_type {}; + template struct concat { L lhs; R rhs; @@ -456,6 +473,21 @@ constexpr auto parse_tail(T head, S format_str) { } } +template struct parse_specs_result { + formatter fmt; + size_t end; +}; + +template +constexpr parse_specs_result parse_specs(basic_string_view str, + size_t pos) { + str.remove_prefix(pos); + auto ctx = basic_format_parse_context(str); + auto f = formatter(); + auto end = f.parse(ctx); + return {f, pos + (end - str.data()) + 1}; +} + // Compiles a non-empty format string and returns the compiled representation // or unknown_format() on unrecognized input. template @@ -471,6 +503,11 @@ constexpr auto compile_format_string(S format_str) { using type = get_type; return parse_tail(field(), format_str); + } else if constexpr (str[POS + 1] == ':') { + using type = get_type; + constexpr auto result = parse_specs(str, POS + 2); + return parse_tail( + spec_field{result.fmt}, format_str); } else { return unknown_format(); } diff --git a/test/compile-test.cc b/test/compile-test.cc index a3f7322d..8004fff3 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -127,7 +127,8 @@ struct formattable {}; FMT_BEGIN_NAMESPACE template <> struct formatter : formatter { - auto format(formattable, format_context& ctx) -> decltype(ctx.out()) { + template + auto format(formattable, FormatContext& ctx) -> decltype(ctx.out()) { return formatter::format("foo", ctx); } }; @@ -154,6 +155,11 @@ TEST(CompileTest, FormatDefault) { EXPECT_EQ("4.2", fmt::format(FMT_COMPILE("{}"), 4.2)); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), "foo")); EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), std::string("foo"))); + EXPECT_EQ("foo", fmt::format(FMT_COMPILE("{}"), formattable())); +} + +TEST(CompileTest, FormatSpecs) { + EXPECT_EQ("42", fmt::format(FMT_COMPILE("{:x}"), 0x42)); } TEST(CompileTest, FormatTo) {