diff --git a/doc/api.rst b/doc/api.rst index 02aa994d83e03..f1c087e6c1674 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/variant.h `: formatting of variants * :ref:`fmt/compile.h `: format string compilation * :ref:`fmt/color.h `: terminal color and text style * :ref:`fmt/os.h `: system APIs @@ -181,8 +182,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/variant.h ` for variant +formatting. To make a user-defined type formattable, specialize the ``formatter`` struct template and implement ``parse`` and ``format`` methods:: @@ -447,6 +449,31 @@ The format syntax is described in :ref:`chrono-specs`. .. doxygenfunction:: gmtime(std::time_t time) +.. _variant-api: + +Variant Formatting +================== + +``fmt/variant.h`` provides formatters for + +* `std::monostate `_ +* `std::variant `_ + +**Example**:: + + #include + + std::variant v0{'x'}; + // Prints "<'x'>" + fmt::print("{}", v0); + + std::variant v1{}; + // Prints "< >" + +.. note:: + + Variant support is only available for C++17 and up. + .. _compile-api: Format string compilation diff --git a/doc/syntax.rst b/doc/syntax.rst index 9bf8dba78e0f6..77d8035ea2476 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/variant.h b/include/fmt/variant.h new file mode 100644 index 0000000000000..782078831be7f --- /dev/null +++ b/include/fmt/variant.h @@ -0,0 +1,107 @@ +// Formatting library for C++ - experimental range support +// +// {fmt} support for variant interface. + +#ifndef FMT_VARIANT_H_ +#define FMT_VARIANT_H_ + +#include + +#include "format.h" +#include "ranges.h" + +#define FMT_HAS_VARIANT FMT_CPLUSPLUS >= 201703L +#if FMT_HAS_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++ = ' '; + return out; + } +}; + +namespace detail { + +template +using variant_index_sequence = make_index_sequence::value>; + +// variant_size and variant_alternative check. +template class is_variant_like_ { + template + static auto check(U* p) -> decltype(std::variant_size::value, int()); + template static void check(...); + + public: + static constexpr const bool value = + !std::is_void(nullptr))>::value; +}; + +// formattable element check +template ::value> +class is_variant_formattable_ { + public: + static constexpr const bool value = false; +}; +template class is_variant_formattable_ { + template + static std::integral_constant< + bool, + (fmt::is_formattable, C>::value && ...)> + check(index_sequence); + + public: + static constexpr const bool value = + decltype(check(variant_index_sequence{}))::value; +}; + +} // 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< + VariantT, Char, + enable_if_t::value && + fmt::is_variant_formattable::value>> { + template + FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } + + template + auto format(const VariantT& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + *out++ = '<'; + std::visit( + [&](const auto& v) { out = detail::write_range_entry(out, v); }, + value); + *out++ = '>'; + return out; + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_VARIANT_H_ + +#endif // FMT_HAS_VARIANT diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 34a693ea24927..0ff6453021f98 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -76,6 +76,7 @@ if (MSVC) endif() add_fmt_test(printf-test) add_fmt_test(ranges-test ranges-odr-test.cc) +add_fmt_test(variant-test) add_fmt_test(scan-test) add_fmt_test(std-test) if (MSVC) diff --git a/test/variant-test.cc b/test/variant-test.cc new file mode 100644 index 0000000000000..0b242408863f5 --- /dev/null +++ b/test/variant-test.cc @@ -0,0 +1,46 @@ +// Formatting library for C++ - experimental variant API +// +// {fmt} support for variant interface. + +#include "fmt/variant.h" + +#include + +#include "gtest/gtest.h" + +#if FMT_HAS_VARIANT + +TEST(variant_test, format_monostate) { + EXPECT_EQ(fmt::format("{}", std::monostate{}), " "); +} +TEST(variant_test, format_variant) { + using V0 = std::variant; + V0 v0(42); + V0 v1(1.5f); + V0 v2("hello"); + V0 v3('i'); + EXPECT_EQ(fmt::format("{}", v0), "<42>"); + EXPECT_EQ(fmt::format("{}", v1), "<1.5>"); + EXPECT_EQ(fmt::format("{}", v2), "<\"hello\">"); + EXPECT_EQ(fmt::format("{}", v3), "<'i'>"); + + enum class noformatenum{b}; + struct noformatstruct{}; + 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_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), "< >"); + EXPECT_EQ(fmt::format("{}", v5), "<\"yes, this is variant\">"); +} + +#endif // FMT_HAS_VARIANT