From 6a775e95604da91d0b97799039e52860964b7dea Mon Sep 17 00:00:00 2001 From: jehelset <47066444+jehelset@users.noreply.github.com> Date: Sun, 26 Jun 2022 16:28:01 +0200 Subject: [PATCH] Add support for 'std::variant' in C++17 (#2941) Add support for 'std::variant' in C++17. For C++17, if all the alternatives of a variant are formattable the variant is now also formattable. In addition 'std::monostate' is now formattable. Moves implementation into 'std.h', and tests into 'std-test.cc'. Avoid fold-expression since MSVC was crashing. Add section for 'fmt/std.h' in API-docs. --- doc/api.rst | 35 ++++++++++++++++- doc/syntax.rst | 2 +- include/fmt/std.h | 95 +++++++++++++++++++++++++++++++++++++++++++++++ test/std-test.cc | 33 ++++++++++++++++ 4 files changed, 162 insertions(+), 3 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index 9a3f9911..bd538cfc 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -12,6 +12,7 @@ The {fmt} library API consists of the following parts: formatting functions and locale support * :ref:`fmt/ranges.h `: formatting of ranges and tuples * :ref:`fmt/chrono.h `: date and time formatting +* :ref:`fmt/std.h `: formatters for standard library types * :ref:`fmt/compile.h `: format string compilation * :ref:`fmt/color.h `: terminal color and text style * :ref:`fmt/os.h `: system APIs @@ -184,8 +185,9 @@ Formatting User-defined Types The {fmt} library provides formatters for many standard C++ types. See :ref:`fmt/ranges.h ` for ranges and tuples including standard -containers such as ``std::vector`` and :ref:`fmt/chrono.h ` for date -and time formatting. +containers such as ``std::vector``, :ref:`fmt/chrono.h ` for date +and time formatting and :ref:`fmt/std.h ` for filesystem and variant +formatting. To make a user-defined type formattable, specialize the ``formatter`` struct template and implement ``parse`` and ``format`` methods:: @@ -445,6 +447,35 @@ The format syntax is described in :ref:`chrono-specs`. .. doxygenfunction:: gmtime(std::time_t time) +.. _std-api: + +Standard Library Types Formatting +================================= + +``fmt/std.h`` provides formatters for: + +* `std::filesystem::path `_ +* `std::thread::id `_ +* `std::monostate `_ +* `std::variant `_ + +Formatting Variants +------------------- + +A ``std::variant`` is only formattable if every variant alternative is formattable, and requires the +``__cpp_lib_variant`` `library feature `_. + +**Example**:: + + #include + + std::variant v0{'x'}; + // Prints "variant('x')" + fmt::print("{}", v0); + + std::variant v1; + // Prints "variant(monostate)" + .. _compile-api: Format string compilation diff --git a/doc/syntax.rst b/doc/syntax.rst index 9bf8dba7..77d8035e 100644 --- a/doc/syntax.rst +++ b/doc/syntax.rst @@ -346,7 +346,7 @@ points are: | | command ``%OS`` produces the locale's alternative representation. | +---------+--------------------------------------------------------------------+ -Specifiers that have a calendaric component such as `'d'` (the day of month) +Specifiers that have a calendaric component such as ``'d'`` (the day of month) are valid only for ``std::tm`` and not durations or time points. .. range-specs: diff --git a/include/fmt/std.h b/include/fmt/std.h index 84e4497a..ccf376a2 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -9,6 +9,8 @@ #define FMT_STD_H_ #include +#include +#include #include "ostream.h" @@ -68,4 +70,97 @@ template struct formatter : basic_ostream_formatter {}; FMT_END_NAMESPACE +#ifdef __cpp_lib_variant +# include + +FMT_BEGIN_NAMESPACE +template struct formatter { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const std::monostate&, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + out = detail::write(out, "monostate"); + return out; + } +}; + +namespace detail { + +template +using variant_index_sequence = + std::make_index_sequence::value>; + +// variant_size and variant_alternative check. +template +struct is_variant_like_ : std::false_type {}; +template +struct is_variant_like_::value)>> + : std::true_type {}; + +// formattable element check +template class is_variant_formattable_ { + template + static std::conjunction< + is_formattable, C>...> + check(std::index_sequence); + + public: + static constexpr const bool value = + decltype(check(variant_index_sequence{}))::value; +}; + +template +auto write_variant_alternative(OutputIt out, const T& v) -> OutputIt { + if constexpr (is_string::value) + return write_escaped_string(out, detail::to_string_view(v)); + else if constexpr (std::is_same_v) + return write_escaped_char(out, v); + else + return write(out, v); +} + +} // namespace detail + +template struct is_variant_like { + static constexpr const bool value = detail::is_variant_like_::value; +}; + +template struct is_variant_formattable { + static constexpr const bool value = + detail::is_variant_formattable_::value; +}; + +template +struct formatter< + Variant, Char, + std::enable_if_t, is_variant_formattable>>> { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const Variant& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + + out = detail::write(out, "variant("); + std::visit( + [&](const auto& v) { + out = detail::write_variant_alternative(out, v); + }, + value); + *out++ = ')'; + return out; + } +}; +FMT_END_NAMESPACE +#endif + #endif // FMT_STD_H_ diff --git a/test/std-test.cc b/test/std-test.cc index 6589027f..02b5a591 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -7,6 +7,8 @@ #include "fmt/std.h" +#include + #include "gtest/gtest.h" TEST(std_test, path) { @@ -32,3 +34,34 @@ TEST(std_test, path) { TEST(std_test, thread_id) { EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty()); } + +TEST(std_test, variant) { +#ifdef __cpp_lib_variant + EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate"); + using V0 = std::variant; + V0 v0(42); + V0 v1(1.5f); + V0 v2("hello"); + V0 v3('i'); + EXPECT_EQ(fmt::format("{}", v0), "variant(42)"); + EXPECT_EQ(fmt::format("{}", v1), "variant(1.5)"); + EXPECT_EQ(fmt::format("{}", v2), "variant(\"hello\")"); + EXPECT_EQ(fmt::format("{}", v3), "variant('i')"); + + struct unformattable {}; + EXPECT_FALSE((fmt::is_formattable::value)); + EXPECT_FALSE((fmt::is_formattable>::value)); + EXPECT_FALSE((fmt::is_formattable>::value)); + EXPECT_FALSE((fmt::is_formattable>::value)); + EXPECT_FALSE( + (fmt::is_formattable>::value)); + EXPECT_TRUE((fmt::is_formattable>::value)); + + using V1 = std::variant; + V1 v4{}; + V1 v5{std::in_place_index<1>, "yes, this is variant"}; + + EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)"); + EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")"); +#endif +}