diff --git a/CMakeLists.txt b/CMakeLists.txt index 61fd8c80..19335e86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,8 +138,8 @@ function(add_headers VAR) endfunction() # Define the fmt library, its includes and the needed defines. -add_headers(FMT_HEADERS color.h core.h format.h format-inl.h ostream.h printf.h - time.h ranges.h) +add_headers(FMT_HEADERS color.h core.h format.h format-inl.h locale.h ostream.h + printf.h time.h ranges.h) set(FMT_SOURCES src/format.cc) if (HAVE_OPEN) add_headers(FMT_HEADERS posix.h) diff --git a/include/fmt/core.h b/include/fmt/core.h index 14df3c1b..916562f2 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -960,6 +960,22 @@ class arg_map { } }; +// A type-erased reference to an std::locale to avoid heavy include. +class locale_ref { + private: + const void *locale_; // A type-erased pointer to std::locale. + friend class locale; + + public: + locale_ref() : locale_(FMT_NULL) {} + + template + explicit locale_ref(const Locale &loc); + + template + Locale get() const; +}; + template class context_base { public: @@ -969,14 +985,16 @@ class context_base { basic_parse_context parse_context_; iterator out_; basic_format_args args_; + locale_ref loc_; protected: typedef Char char_type; typedef basic_format_arg format_arg; context_base(OutputIt out, basic_string_view format_str, - basic_format_args ctx_args) - : parse_context_(format_str), out_(out), args_(ctx_args) {} + basic_format_args ctx_args, + locale_ref loc = locale_ref()) + : parse_context_(format_str), out_(out), args_(ctx_args), loc_(loc) {} // Returns the argument with specified index. format_arg do_get_arg(unsigned arg_id) { @@ -1009,6 +1027,8 @@ class context_base { // Advances the begin iterator to ``it``. void advance_to(iterator it) { out_ = it; } + + locale_ref locale() { return loc_; } }; template @@ -1078,8 +1098,9 @@ class basic_format_context : stored in the object so make sure they have appropriate lifetimes. */ basic_format_context(OutputIt out, basic_string_view format_str, - basic_format_args ctx_args) - : base(out, format_str, ctx_args) {} + basic_format_args ctx_args, + internal::locale_ref loc = internal::locale_ref()) + : base(out, format_str, ctx_args, loc) {} format_arg next_arg() { return this->do_get_arg(this->parse_context().next_arg_id()); diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 9a9d9dbd..513834f6 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -203,25 +203,28 @@ FMT_FUNC size_t internal::count_code_points(basic_string_view s) { } #if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -class locale { - private: - std::locale locale_; - - public: - explicit locale(std::locale loc = std::locale()) : locale_(loc) {} - std::locale get() { return locale_; } -}; - namespace internal { + +template +locale_ref::locale_ref(const Locale &loc) : locale_(&loc) { + static_assert(std::is_same::value, ""); +} + +template +Locale locale_ref::get() const { + static_assert(std::is_same::value, ""); + return locale_ ? *static_cast(locale_) : std::locale(); +} + template -FMT_FUNC Char thousands_sep_impl(locale_provider *lp) { - std::locale loc = lp ? lp->locale().get() : std::locale(); - return std::use_facet>(loc).thousands_sep(); +FMT_FUNC Char thousands_sep_impl(locale_ref loc) { + return std::use_facet >( + loc.get()).thousands_sep(); } } #else template -FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { +FMT_FUNC Char internal::thousands_sep(locale_ref) { return FMT_STATIC_THOUSANDS_SEPARATOR; } #endif @@ -959,10 +962,6 @@ FMT_FUNC void vprint(wstring_view format_str, wformat_args args) { vprint(stdout, format_str, args); } -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -FMT_FUNC locale locale_provider::locale() { return fmt::locale(); } -#endif - FMT_END_NAMESPACE #ifdef _MSC_VER diff --git a/include/fmt/format.h b/include/fmt/format.h index 66d8df5d..5f055dab 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -422,16 +422,6 @@ inline u8string_view operator"" _u(const char *s, std::size_t n) { } #endif -// A wrapper around std::locale used to reduce compile times since -// is very heavy. -class locale; - -class locale_provider { - public: - virtual ~locale_provider() {} - virtual fmt::locale locale(); -}; - // The number of characters to store in the basic_memory_buffer object itself // to avoid dynamic memory allocation. enum { inline_buffer_size = 500 }; @@ -1034,16 +1024,16 @@ class add_thousands_sep { }; template -FMT_API Char thousands_sep_impl(locale_provider *lp); +FMT_API Char thousands_sep_impl(locale_ref loc); template -inline Char thousands_sep(locale_provider *lp) { - return Char(thousands_sep_impl(lp)); +inline Char thousands_sep(locale_ref loc) { + return Char(thousands_sep_impl(loc)); } template <> -inline wchar_t thousands_sep(locale_provider *lp) { - return thousands_sep_impl(lp); +inline wchar_t thousands_sep(locale_ref loc) { + return thousands_sep_impl(loc); } // Formats a decimal unsigned integer value writing into buffer. @@ -1449,7 +1439,8 @@ class arg_formatter_base { } public: - arg_formatter_base(Range r, format_specs *s): writer_(r), specs_(s) {} + arg_formatter_base(Range r, format_specs *s, locale_ref loc) + : writer_(r, loc), specs_(s) {} iterator operator()(monostate) { FMT_ASSERT(false, "invalid argument type"); @@ -2320,7 +2311,7 @@ class arg_formatter: \endrst */ explicit arg_formatter(context_type &ctx, format_specs *spec = FMT_NULL) - : base(Range(ctx.out()), spec), ctx_(ctx) {} + : base(Range(ctx.out()), spec, ctx.locale()), ctx_(ctx) {} // Deprecated. arg_formatter(context_type &ctx, format_specs &spec) @@ -2408,7 +2399,7 @@ class basic_writer { private: iterator out_; // Output iterator. - std::unique_ptr locale_; + internal::locale_ref locale_; iterator out() const { return out_; } @@ -2608,7 +2599,7 @@ class basic_writer { void on_num() { unsigned num_digits = internal::count_digits(abs_value); - char_type sep = internal::thousands_sep(writer.locale_.get()); + char_type sep = internal::thousands_sep(writer.locale_); unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); writer.write_int(size, get_prefix(), spec, num_writer{abs_value, size, sep}); @@ -2698,7 +2689,9 @@ class basic_writer { public: /** Constructs a ``basic_writer`` object. */ - explicit basic_writer(Range out): out_(out.begin()) {} + explicit basic_writer( + Range out, internal::locale_ref loc = internal::locale_ref()) + : out_(out.begin()), locale_(loc) {} void write(int value) { write_decimal(value); } void write(long value) { write_decimal(value); } @@ -3226,8 +3219,9 @@ struct format_handler : internal::error_handler { typedef typename ArgFormatter::range range; format_handler(range r, basic_string_view str, - basic_format_args format_args) - : context(r.begin(), str, format_args) {} + basic_format_args format_args, + internal::locale_ref loc) + : context(r.begin(), str, format_args, loc) {} void on_text(const Char *begin, const Char *end) { auto size = internal::to_unsigned(end - begin); @@ -3277,10 +3271,12 @@ struct format_handler : internal::error_handler { /** Formats arguments and writes the output to the range. */ template -typename Context::iterator vformat_to(typename ArgFormatter::range out, - basic_string_view format_str, - basic_format_args args) { - format_handler h(out, format_str, args); +typename Context::iterator vformat_to( + typename ArgFormatter::range out, + basic_string_view format_str, + basic_format_args args, + internal::locale_ref loc = internal::locale_ref()) { + format_handler h(out, format_str, args, loc); internal::parse_format_string(format_str, h); return h.context.out(); } diff --git a/include/fmt/locale.h b/include/fmt/locale.h new file mode 100644 index 00000000..9b8d29ae --- /dev/null +++ b/include/fmt/locale.h @@ -0,0 +1,47 @@ +// Formatting library for C++ - std::locale support +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_LOCALE_H_ +#define FMT_LOCALE_H_ + +#include "format.h" +#include + +FMT_BEGIN_NAMESPACE + +namespace internal { +template +typename buffer_context::type::iterator vformat_to( + const std::locale &loc, basic_buffer &buf, + basic_string_view format_str, + basic_format_args::type> args) { + typedef back_insert_range > range; + return vformat_to>( + buf, to_string_view(format_str), args, internal::locale_ref(loc)); +} + +template +std::basic_string vformat( + const std::locale &loc, basic_string_view format_str, + basic_format_args::type> args) { + basic_memory_buffer buffer; + internal::vformat_to(loc, buffer, format_str, args); + return fmt::to_string(buffer); +} +} + +template +inline std::basic_string format( + const std::locale &loc, const S &format_str, const Args &... args) { + return internal::vformat( + loc, to_string_view(format_str), + *internal::checked_args(format_str, args...)); +} + +FMT_END_NAMESPACE + +#endif // FMT_LOCALE_H_ diff --git a/include/fmt/ostream.h b/include/fmt/ostream.h index fa26f6d2..84b31cc5 100644 --- a/include/fmt/ostream.h +++ b/include/fmt/ostream.h @@ -1,6 +1,6 @@ // Formatting library for C++ - std::ostream support // -// Copyright (c) 2012 - 2016, Victor Zverovich +// Copyright (c) 2012 - present, Victor Zverovich // All rights reserved. // // For the license information refer to format.h. diff --git a/include/fmt/printf.h b/include/fmt/printf.h index b50c8faf..e8caecaa 100644 --- a/include/fmt/printf.h +++ b/include/fmt/printf.h @@ -243,7 +243,8 @@ class printf_arg_formatter: */ printf_arg_formatter(internal::basic_buffer &buffer, format_specs &spec, context_type &ctx) - : base(back_insert_range>(buffer), &spec), + : base(back_insert_range>(buffer), &spec, + ctx.locale()), context_(ctx) {} template diff --git a/src/format.cc b/src/format.cc index e37c61cf..33825818 100644 --- a/src/format.cc +++ b/src/format.cc @@ -9,10 +9,11 @@ FMT_BEGIN_NAMESPACE template struct internal::basic_data; +template internal::locale_ref::locale_ref(const std::locale &loc); // Explicit instantiations for char. -template FMT_API char internal::thousands_sep_impl(locale_provider *lp); +template FMT_API char internal::thousands_sep_impl(locale_ref); template void internal::basic_buffer::append(const char *, const char *); @@ -38,7 +39,7 @@ template FMT_API void internal::sprintf_format( // Explicit instantiations for wchar_t. -template FMT_API wchar_t internal::thousands_sep_impl(locale_provider *); +template FMT_API wchar_t internal::thousands_sep_impl(locale_ref); template void internal::basic_buffer::append( const wchar_t *, const wchar_t *); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 71efeeb1..2a108aaf 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -89,6 +89,7 @@ add_fmt_test(core-test) add_fmt_test(gtest-extra-test) add_fmt_test(format-test mock-allocator.h) add_fmt_test(format-impl-test) +add_fmt_test(locale-test) add_fmt_test(ostream-test) add_fmt_test(printf-test) add_fmt_test(time-test) diff --git a/test/format-test.cc b/test/format-test.cc index ffdf72b7..21ed824e 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -603,17 +603,6 @@ TEST(StringViewTest, Ctor) { EXPECT_EQ(4u, string_view(std::string("defg")).size()); } -// GCC 4.6 doesn't have std::is_copy_*. -#if FMT_GCC_VERSION && FMT_GCC_VERSION >= 407 -TEST(WriterTest, NotCopyConstructible) { - EXPECT_FALSE(std::is_copy_constructible::value); -} - -TEST(WriterTest, NotCopyAssignable) { - EXPECT_FALSE(std::is_copy_assignable::value); -} -#endif - TEST(WriterTest, Data) { memory_buffer buf; fmt::writer w(buf); @@ -1947,7 +1936,7 @@ class mock_arg_formatter: typedef buffer_range range; mock_arg_formatter(fmt::format_context &ctx, fmt::format_specs *s = FMT_NULL) - : base(fmt::internal::get_container(ctx.out()), s) { + : base(fmt::internal::get_container(ctx.out()), s, ctx.locale()) { EXPECT_CALL(*this, call(42)); } diff --git a/test/locale-test.cc b/test/locale-test.cc new file mode 100644 index 00000000..1abc8c3f --- /dev/null +++ b/test/locale-test.cc @@ -0,0 +1,21 @@ +// Formatting library for C++ - core tests +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#include "fmt/locale.h" +#include "gmock.h" + +struct numpunct : std::numpunct { + protected: + char do_thousands_sep() const FMT_OVERRIDE { return '~'; } +}; + +TEST(LocaleTest, Format) { + std::locale loc; + EXPECT_EQ("1~234~567", + fmt::format(std::locale(loc, new numpunct()), "{:n}", 1234567)); + EXPECT_EQ("1,234,567", fmt::format(loc, "{:n}", 1234567)); +}