diff --git a/doc/api.rst b/doc/api.rst index 1402f7c08758..2e7f6d6d146c 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -508,6 +508,7 @@ Standard Library Types Formatting * `std::thread::id `_ * `std::monostate `_ * `std::variant `_ +* `std::optional `_ Formatting Variants ------------------- diff --git a/include/fmt/std.h b/include/fmt/std.h index 32c3e454e293..544ec541140f 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -29,6 +29,9 @@ # if FMT_HAS_INCLUDE() # include # endif +# if FMT_HAS_INCLUDE() +# include +# endif #endif // GCC 4 does not support FMT_HAS_INCLUDE. @@ -91,6 +94,49 @@ template struct formatter : basic_ostream_formatter {}; FMT_END_NAMESPACE +#ifdef __cpp_lib_optional +FMT_BEGIN_NAMESPACE +template +struct formatter, Char, + std::enable_if_t::value>> { + private: + formatter underlying_; + static constexpr basic_string_view optional = + detail::string_literal{}; + 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: + template FMT_CONSTEXPR auto parse(ParseContext& ctx) { + maybe_set_debug_format(underlying_, true); + return underlying_.parse(ctx); + } + + template + auto format(std::optional const& opt, FormatContext& ctx) const + -> decltype(ctx.out()) { + if (not opt) return detail::write(ctx.out(), none); + + auto out = ctx.out(); + out = detail::write(out, optional); + ctx.advance_to(out); + out = underlying_.format(*opt, ctx); + return detail::write(out, ')'); + } +}; +FMT_END_NAMESPACE +#endif // __cpp_lib_optional + #ifdef __cpp_lib_variant FMT_BEGIN_NAMESPACE template struct formatter { diff --git a/test/std-test.cc b/test/std-test.cc index 4411d3d1cd39..a5a4dcc6cd16 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -12,6 +12,7 @@ #include "fmt/os.h" // fmt::system_category #include "fmt/ranges.h" +#include "fmt/xchar.h" #include "gtest-extra.h" // StartsWith using testing::StartsWith; @@ -50,6 +51,35 @@ TEST(std_test, thread_id) { EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty()); } +TEST(std_test, optional) { +#ifdef __cpp_lib_optional + EXPECT_EQ(fmt::format(L"{}", std::optional{}), L"none"); + EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")"); + EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2}, + std::optional{3}}), + "[optional(1), optional(2), optional(3)]"); + EXPECT_EQ( + fmt::format("{}", std::optional>{std::optional{ + "nested"}}), + "optional(optional(\"nested\"))"); + EXPECT_EQ( + fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30), + "optional(\"left aligned\" )"); + EXPECT_EQ( + fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}), + "optional([104, 101, 108, 108, 111])"); + EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}), + "optional(\"string\")"); + EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')"); + EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)"); + + struct unformattable {}; + EXPECT_FALSE((fmt::is_formattable::value)); + EXPECT_FALSE((fmt::is_formattable>::value)); + EXPECT_TRUE((fmt::is_formattable>::value)); +#endif +} + TEST(std_test, variant) { #ifdef __cpp_lib_variant EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");