diff --git a/doc/api.rst b/doc/api.rst index 1402f7c0..2e7f6d6d 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 d2a86fb1..882e9a86 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 (!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 4411d3d1..39aac5a2 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -50,6 +50,34 @@ 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("{}", std::optional{}), "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>{{"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"); diff --git a/test/xchar-test.cc b/test/xchar-test.cc index 1deab2d0..022646da 100644 --- a/test/xchar-test.cc +++ b/test/xchar-test.cc @@ -16,6 +16,7 @@ #include "fmt/color.h" #include "fmt/ostream.h" #include "fmt/ranges.h" +#include "fmt/std.h" #include "gtest-extra.h" // Contains #include "util.h" // get_locale @@ -588,4 +589,12 @@ TEST(locale_test, sign) { EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50"); } +TEST(std_test_xchar, optional) { +# ifdef __cpp_lib_optional + EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')"); + EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}), + L"optional(\"wide string\")"); +# endif +} + #endif // FMT_STATIC_THOUSANDS_SEPARATOR