diff --git a/include/fmt/compile.h b/include/fmt/compile.h index da2ad874..9cd6c937 100644 --- a/include/fmt/compile.h +++ b/include/fmt/compile.h @@ -172,11 +172,12 @@ template auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) -> typename Context::iterator { using char_type = typename Context::char_type; - basic_parse_context parse_ctx(to_string_view(cf.format_)); + basic_parse_context parse_ctx(to_string_view(cf.format_str_)); Context ctx(out.begin(), args); - const auto& parts = cf.parts_provider_.parts(); - for (auto part_it = parts.begin(); part_it != parts.end(); ++part_it) { + const auto& parts = cf.parts(); + for (auto part_it = std::begin(parts); part_it != std::end(parts); + ++part_it) { const auto& part = *part_it; const auto& value = part.val; @@ -188,7 +189,8 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) auto&& it = reserve(output, text.size()); it = std::copy_n(text.begin(), text.size(), it); ctx.advance_to(output); - } break; + break; + } case format_part_t::kind::arg_index: advance_to(parse_ctx, part.arg_id_end); @@ -222,18 +224,92 @@ auto vformat_to(Range out, CompiledFormat& cf, basic_format_args args) advance_to(parse_ctx, part.arg_id_end); ctx.advance_to( visit_format_arg(arg_formatter(ctx, nullptr, &specs), arg)); - } break; + break; + } } } return ctx.out(); } } // namespace cf -template -class compiled_format { +template struct compiled_format_base { + using char_type = char_t; + using parts_container = std::vector>; + + parts_container compiled_parts; + + explicit compiled_format_base(basic_string_view format_str) { + compile_format_string(format_str, + [this](const format_part& part) { + compiled_parts.push_back(part); + }); + } + + const parts_container& parts() const { return compiled_parts; } +}; + +template struct format_part_array { + format_part data[N] = {}; + FMT_CONSTEXPR format_part_array() = default; +}; + +template +FMT_CONSTEXPR format_part_array compile_to_parts( + basic_string_view format_str) { + format_part_array parts; + unsigned counter = 0; + // This is not a lambda for compatibility with older compilers. + struct { + format_part* parts; + unsigned* counter; + FMT_CONSTEXPR void operator()(const format_part& part) { + parts[(*counter)++] = part; + } + } collector{parts.data, &counter}; + compile_format_string(format_str, collector); + if (counter < N) { + parts.data[counter] = + format_part::make_text(basic_string_view()); + } + return parts; +} + +template constexpr const T& constexpr_max(const T& a, const T& b) { + return (a < b) ? b : a; +} + +template +struct compiled_format_base::value>> { + using char_type = char_t; + + FMT_CONSTEXPR explicit compiled_format_base(basic_string_view) {} + +// Workaround for old compilers. Format string compilation will not be +// performed there anyway. +#if FMT_USE_CONSTEXPR + static FMT_CONSTEXPR_DECL const unsigned num_format_parts = + constexpr_max(count_parts(to_string_view(S())), 1u); +#else + static const unsigned num_format_parts = 1; +#endif + + using parts_container = format_part[num_format_parts]; + + const parts_container& parts() const { + static FMT_CONSTEXPR_DECL const auto compiled_parts = + compile_to_parts( + internal::to_string_view(S())); + return compiled_parts.data; + } +}; + +template +class compiled_format : private compiled_format_base { + public: + using typename compiled_format_base::char_type; + private: - S format_; - PreparedPartsProvider parts_provider_; + basic_string_view format_str_; template friend auto cf::vformat_to(Range out, CompiledFormat& cf, @@ -241,95 +317,9 @@ class compiled_format { typename Context::iterator; public: - using char_type = char_t; - compiled_format() = delete; - constexpr compiled_format(S f) - : format_(std::move(f)), parts_provider_(to_string_view(format_)) {} -}; - -template class compiletime_prepared_parts_type_provider { - private: - using char_type = char_t; - -// Workaround for old compilers. Compiletime parts preparation will not be -// performed with them anyway. -#if FMT_USE_CONSTEXPR - static FMT_CONSTEXPR_DECL const unsigned number_of_format_parts = - count_parts(to_string_view(Format())); -#else - static const unsigned number_of_format_parts = 0u; -#endif - - public: - template struct format_parts_array { - using value_type = format_part; - - FMT_CONSTEXPR format_parts_array() : arr{} {} - - FMT_CONSTEXPR value_type& operator[](unsigned ind) { return arr[ind]; } - - FMT_CONSTEXPR const value_type* begin() const { return arr; } - FMT_CONSTEXPR const value_type* end() const { return begin() + N; } - - private: - value_type arr[N]; - }; - - struct empty { - // Parts preparator will search for it - using value_type = format_part; - }; - - using type = conditional_t, empty>; -}; - -template -FMT_CONSTEXPR PartsContainer -prepare_compiletime_parts(basic_string_view format_str) { - // This is not a lambda for compatibility with older compilers. - struct collector { - PartsContainer& parts; - unsigned counter = 0; - FMT_CONSTEXPR void operator()(const format_part& part) { - parts[counter++] = part; - } - }; - PartsContainer parts; - compile_format_string(format_str, collector{parts}); - return parts; -} - -template class runtime_parts_provider { - public: - using parts_container = std::vector>; - - runtime_parts_provider(basic_string_view format_str) { - compile_format_string( - format_str, - [this](const format_part& part) { parts_.push_back(part); }); - } - - const parts_container& parts() const { return parts_; } - - private: - parts_container parts_; -}; - -template struct compiletime_parts_provider { - using parts_container = - typename internal::compiletime_prepared_parts_type_provider::type; - - template - FMT_CONSTEXPR compiletime_parts_provider(basic_string_view) {} - - const parts_container& parts() const { - static FMT_CONSTEXPR_DECL const parts_container prepared_parts = - prepare_compiletime_parts( - internal::to_string_view(Format())); - return prepared_parts; - } + explicit constexpr compiled_format(basic_string_view format_str) + : compiled_format_base(format_str), format_str_(format_str) {} }; } // namespace internal @@ -337,17 +327,17 @@ template struct compiletime_parts_provider { template ::value)> FMT_CONSTEXPR auto compile(S format_str) - -> internal::compiled_format, - Args...> { - return format_str; + -> internal::compiled_format { + return internal::compiled_format(to_string_view(format_str)); } #endif // Compiles the format string which must be a string literal. template -auto compile(const Char (&format_str)[N]) -> internal::compiled_format< - std::basic_string, internal::runtime_parts_provider, Args...> { - return std::basic_string(format_str, N - 1); +auto compile(const Char (&format_str)[N]) + -> internal::compiled_format { + return internal::compiled_format( + basic_string_view(format_str, N - 1)); } template format_to_n(OutputIt out, size_t n, template std::size_t formatted_size(const CompiledFormat& cf, const Args&... args) { - return fmt::format_to( + return format_to( internal::counting_iterator(), cf, args...) .count(); diff --git a/test/compile-test.cc b/test/compile-test.cc index 15ee7a28..d1adc9f2 100644 --- a/test/compile-test.cc +++ b/test/compile-test.cc @@ -39,14 +39,12 @@ using testing::StrictMock; #if FMT_USE_CONSTEXPR template void check_prepared_parts_type(Format format) { - typedef fmt::internal::compiletime_prepared_parts_type_provider - provider; - typedef typename provider::template format_parts_array - expected_parts_type; - static_assert( - std::is_same::value, - "CompileTimePreparedPartsTypeProvider test failed"); + typedef fmt::internal::compiled_format_base provider; + typedef fmt::internal::format_part + expected_parts_type[EXPECTED_PARTS_COUNT]; + static_assert(std::is_same::value, + "CompileTimePreparedPartsTypeProvider test failed"); } TEST(CompileTest, CompileTimePreparedPartsTypeProvider) { @@ -122,8 +120,7 @@ TEST(CompileTest, FormattedSize) { struct formattable {}; FMT_BEGIN_NAMESPACE -template <> -struct formatter : formatter { +template <> struct formatter : formatter { auto format(formattable, format_context& ctx) -> decltype(ctx.out()) { return formatter::format("foo", ctx); } @@ -134,3 +131,8 @@ TEST(CompileTest, FormatUserDefinedType) { auto f = fmt::compile("{}"); EXPECT_EQ(fmt::format(f, formattable()), "foo"); } + +TEST(CompileTest, EmptyFormatString) { + auto f = fmt::compile<>(""); + EXPECT_EQ(fmt::format(f), ""); +}