Skip to content

Commit 5b83020

Browse files
Add optional support (#3303)
1 parent 3a69529 commit 5b83020

File tree

4 files changed

+84
-0
lines changed

4 files changed

+84
-0
lines changed

doc/api.rst

+1
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,7 @@ Standard Library Types Formatting
508508
* `std::thread::id <https://en.cppreference.com/w/cpp/thread/thread/id>`_
509509
* `std::monostate <https://en.cppreference.com/w/cpp/utility/variant/monostate>`_
510510
* `std::variant <https://en.cppreference.com/w/cpp/utility/variant/variant>`_
511+
* `std::optional <https://en.cppreference.com/w/cpp/utility/optional>`_
511512

512513
Formatting Variants
513514
-------------------

include/fmt/std.h

+46
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
# if FMT_HAS_INCLUDE(<variant>)
3030
# include <variant>
3131
# endif
32+
# if FMT_HAS_INCLUDE(<optional>)
33+
# include <optional>
34+
# endif
3235
#endif
3336

3437
// GCC 4 does not support FMT_HAS_INCLUDE.
@@ -91,6 +94,49 @@ template <typename Char>
9194
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
9295
FMT_END_NAMESPACE
9396

97+
#ifdef __cpp_lib_optional
98+
FMT_BEGIN_NAMESPACE
99+
template <typename T, typename Char>
100+
struct formatter<std::optional<T>, Char,
101+
std::enable_if_t<is_formattable<T, Char>::value>> {
102+
private:
103+
formatter<T, Char> underlying_;
104+
static constexpr basic_string_view<Char> optional =
105+
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
106+
'('>{};
107+
static constexpr basic_string_view<Char> none =
108+
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
109+
110+
template <class U>
111+
FMT_CONSTEXPR static auto maybe_set_debug_format(U& u, bool set)
112+
-> decltype(u.set_debug_format(set)) {
113+
u.set_debug_format(set);
114+
}
115+
116+
template <class U>
117+
FMT_CONSTEXPR static void maybe_set_debug_format(U&, ...) {}
118+
119+
public:
120+
template <typename ParseContext> FMT_CONSTEXPR auto parse(ParseContext& ctx) {
121+
maybe_set_debug_format(underlying_, true);
122+
return underlying_.parse(ctx);
123+
}
124+
125+
template <typename FormatContext>
126+
auto format(std::optional<T> const& opt, FormatContext& ctx) const
127+
-> decltype(ctx.out()) {
128+
if (!opt) return detail::write<Char>(ctx.out(), none);
129+
130+
auto out = ctx.out();
131+
out = detail::write<Char>(out, optional);
132+
ctx.advance_to(out);
133+
out = underlying_.format(*opt, ctx);
134+
return detail::write(out, ')');
135+
}
136+
};
137+
FMT_END_NAMESPACE
138+
#endif // __cpp_lib_optional
139+
94140
#ifdef __cpp_lib_variant
95141
FMT_BEGIN_NAMESPACE
96142
template <typename Char> struct formatter<std::monostate, Char> {

test/std-test.cc

+28
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,34 @@ TEST(std_test, thread_id) {
5050
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
5151
}
5252

53+
TEST(std_test, optional) {
54+
#ifdef __cpp_lib_optional
55+
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
56+
EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")");
57+
EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2},
58+
std::optional{3}}),
59+
"[optional(1), optional(2), optional(3)]");
60+
EXPECT_EQ(
61+
fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}),
62+
"optional(optional(\"nested\"))");
63+
EXPECT_EQ(
64+
fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30),
65+
"optional(\"left aligned\" )");
66+
EXPECT_EQ(
67+
fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}),
68+
"optional([104, 101, 108, 108, 111])");
69+
EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}),
70+
"optional(\"string\")");
71+
EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')");
72+
EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)");
73+
74+
struct unformattable {};
75+
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
76+
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
77+
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
78+
#endif
79+
}
80+
5381
TEST(std_test, variant) {
5482
#ifdef __cpp_lib_variant
5583
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");

test/xchar-test.cc

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "fmt/color.h"
1717
#include "fmt/ostream.h"
1818
#include "fmt/ranges.h"
19+
#include "fmt/std.h"
1920
#include "gtest-extra.h" // Contains
2021
#include "util.h" // get_locale
2122

@@ -588,4 +589,12 @@ TEST(locale_test, sign) {
588589
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
589590
}
590591

592+
TEST(std_test_xchar, optional) {
593+
# ifdef __cpp_lib_optional
594+
EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
595+
EXPECT_EQ(fmt::format(L"{}", std::optional{std::wstring{L"wide string"}}),
596+
L"optional(\"wide string\")");
597+
# endif
598+
}
599+
591600
#endif // FMT_STATIC_THOUSANDS_SEPARATOR

0 commit comments

Comments
 (0)