diff --git a/include/fmt/base.h b/include/fmt/base.h index d7679757..b9fe4e8f 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -923,8 +923,11 @@ class locale_ref { public: constexpr locale_ref() : locale_(nullptr) {} - template - locale_ref(const Locale& loc); + template + locale_ref(const Locale& loc) : locale_(&loc) { + // Check if std::isalpha is found via ADL to reduce the chance of misuse. + isalpha('x', loc); + } inline explicit operator bool() const noexcept { return locale_ != nullptr; } #endif // FMT_USE_LOCALE diff --git a/include/fmt/color.h b/include/fmt/color.h index b69c1488..2cbc53ca 100644 --- a/include/fmt/color.h +++ b/include/fmt/color.h @@ -429,7 +429,7 @@ template struct ansi_color_escape { private: static constexpr size_t num_emphases = 8; - Char buffer[7u + 4u * num_emphases]; + Char buffer[7u + 4u * num_emphases] = {}; size_t size = 0; static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out, diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h index 227dd9bb..945cb912 100644 --- a/include/fmt/format-inl.h +++ b/include/fmt/format-inl.h @@ -47,11 +47,6 @@ using std::locale; using std::numpunct; using std::use_facet; } // namespace detail - -template > -locale_ref::locale_ref(const Locale& loc) : locale_(&loc) { - static_assert(std::is_same::value, ""); -} #else namespace detail { struct locale {}; diff --git a/include/fmt/format.h b/include/fmt/format.h index 0cd3a232..4a653007 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -763,6 +763,14 @@ template struct allocator : private std::decay { } }; +template +FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) + -> decltype(f.set_debug_format(set)) { + f.set_debug_format(set); +} +template +FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} + } // namespace detail FMT_BEGIN_EXPORT diff --git a/include/fmt/os.h b/include/fmt/os.h index 40cdcdd4..9455d9d3 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -365,17 +365,17 @@ FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size(); /// A fast buffered output stream for writing from a single thread. Writing from /// multiple threads without external synchronization may result in a data race. -class FMT_API ostream : private detail::buffer { +class ostream : private detail::buffer { private: file file_; - ostream(cstring_view path, const detail::ostream_params& params); + FMT_API ostream(cstring_view path, const detail::ostream_params& params); - static void grow(buffer& buf, size_t); + FMT_API static void grow(buffer& buf, size_t); public: - ostream(ostream&& other) noexcept; - ~ostream(); + FMT_API ostream(ostream&& other) noexcept; + FMT_API ~ostream(); operator writer() { detail::buffer& buf = *this; diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index 0823cbf2..36b38e29 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -241,14 +241,6 @@ using range_reference_type = template using uncvref_type = remove_cvref_t>; -template -FMT_CONSTEXPR auto maybe_set_debug_format(Formatter& f, bool set) - -> decltype(f.set_debug_format(set)) { - f.set_debug_format(set); -} -template -FMT_CONSTEXPR void maybe_set_debug_format(Formatter&, ...) {} - template struct range_format_kind_ : std::integral_constant& quoted, #endif // FMT_CPP_LIB_FILESYSTEM #if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT -template -auto write_escaped_alternative(OutputIt out, const T& v) -> OutputIt { + +template +auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx) + -> OutputIt { if constexpr (has_to_string_view::value) return write_escaped_string(out, detail::to_string_view(v)); if constexpr (std::is_same_v) return write_escaped_char(out, v); - return write(out, v); + + formatter, Char> underlying; + maybe_set_debug_format(underlying, true); + return underlying.format(v, ctx); } #endif @@ -382,18 +387,9 @@ struct formatter, Char, static constexpr basic_string_view none = detail::string_literal{}; - template - FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set) - -> decltype(u.set_debug_format(set)) { - u.set_debug_format(set); - } - - template - FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {} - public: FMT_CONSTEXPR auto parse(parse_context& ctx) { - maybe_set_debug_format(underlying_, true); + detail::maybe_set_debug_format(underlying_, true); return underlying_.parse(ctx); } @@ -429,10 +425,10 @@ struct formatter, Char, if (value.has_value()) { out = detail::write(out, "expected("); if constexpr (!std::is_void::value) - out = detail::write_escaped_alternative(out, *value); + out = detail::write_escaped_alternative(out, *value, ctx); } else { out = detail::write(out, "unexpected("); - out = detail::write_escaped_alternative(out, value.error()); + out = detail::write_escaped_alternative(out, value.error(), ctx); } *out++ = ')'; return out; @@ -496,7 +492,7 @@ struct formatter(out, v); + out = detail::write_escaped_alternative(out, v, ctx); }, value); } @@ -517,6 +513,8 @@ template <> struct formatter { bool debug_ = false; public: + FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; } + FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* { auto it = ctx.begin(), end = ctx.end(); if (it == end) return it; diff --git a/src/format.cc b/src/format.cc index 05d0105b..526082e3 100644 --- a/src/format.cc +++ b/src/format.cc @@ -10,7 +10,7 @@ FMT_BEGIN_NAMESPACE #if FMT_USE_LOCALE -template FMT_API locale_ref::locale_ref(const std::locale& loc); +template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED! template FMT_API auto locale_ref::get() const -> std::locale; #endif diff --git a/test/std-test.cc b/test/std-test.cc index 4bd8abce..18f6bd3f 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -13,6 +13,7 @@ #include #include "fmt/os.h" // fmt::system_category +#include "fmt/ranges.h" #include "gtest-extra.h" // StartsWith #ifdef __cpp_lib_filesystem @@ -197,7 +198,33 @@ class my_class { return fmt::to_string(elm.av); } }; + +class my_class_int { + public: + int av; + + private: + friend auto format_as(const my_class_int& elm) -> int { return elm.av; } +}; } // namespace my_nso + +TEST(std_test, expected_format_as) { +#ifdef __cpp_lib_expected + EXPECT_EQ( + fmt::format( + "{}", std::expected{my_nso::my_number::one}), + "expected(\"first\")"); + EXPECT_EQ( + fmt::format("{}", + std::expected{my_nso::my_class{7}}), + "expected(\"7\")"); + EXPECT_EQ(fmt::format("{}", + std::expected{ + my_nso::my_class_int{8}}), + "expected(8)"); +#endif +} + TEST(std_test, optional_format_as) { #ifdef __cpp_lib_optional EXPECT_EQ(fmt::format("{}", std::optional{}), "none"); @@ -206,6 +233,8 @@ TEST(std_test, optional_format_as) { EXPECT_EQ(fmt::format("{}", std::optional{}), "none"); EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}), "optional(\"7\")"); + EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}), + "optional(8)"); #endif } @@ -275,6 +304,24 @@ TEST(std_test, variant) { #endif } +TEST(std_test, variant_format_as) { +#ifdef __cpp_lib_variant + + EXPECT_EQ(fmt::format("{}", std::variant{}), + "variant(\"first\")"); + EXPECT_EQ(fmt::format( + "{}", std::variant{my_nso::my_number::one}), + "variant(\"first\")"); + EXPECT_EQ( + fmt::format("{}", std::variant{my_nso::my_class{7}}), + "variant(\"7\")"); + EXPECT_EQ( + fmt::format("{}", + std::variant{my_nso::my_class_int{8}}), + "variant(8)"); +#endif +} + TEST(std_test, error_code) { auto& generic = std::generic_category(); EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42"); @@ -289,6 +336,10 @@ TEST(std_test, error_code) { EXPECT_EQ(fmt::format("{:s}", ec), ec.message()); EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)), "\"generic:42\""); + EXPECT_EQ(fmt::format("{}", + std::map{ + {std::error_code(42, generic), 0}}), + "{\"generic:42\": 0}"); } template void exception_test() {