From 594ec8e1c05afe357b6f654876b324a1e05b9380 Mon Sep 17 00:00:00 2001 From: John Eivind Helset Date: Wed, 22 Jun 2022 06:03:03 +0200 Subject: [PATCH] Adjust to PR feedback. Moves implementation into 'std.h', and tests into 'std-test.cc'. Avoid fold-expression since MSVC was crashing. Hopefully works with this. Add secion for 'fmt/std.h' in API-docs. --- doc/api.rst | 24 ++++---- include/fmt/std.h | 127 ++++++++++++++++++++++++++++++++++++++++++ include/fmt/variant.h | 107 ----------------------------------- test/CMakeLists.txt | 1 - test/std-test.cc | 33 +++++++++++ test/variant-test.cc | 43 -------------- 6 files changed, 174 insertions(+), 161 deletions(-) delete mode 100644 include/fmt/variant.h delete mode 100644 test/variant-test.cc diff --git a/doc/api.rst b/doc/api.rst index f1c087e6..397e846a 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -12,7 +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/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 @@ -183,7 +183,7 @@ 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``, :ref:`fmt/chrono.h ` for date -and time formatting and :ref:`fmt/variant.h ` for variant +and time formatting and :ref:`fmt/std.h ` for filesystem and variant formatting. To make a user-defined type formattable, specialize the ``formatter`` struct @@ -449,16 +449,24 @@ The format syntax is described in :ref:`chrono-specs`. .. doxygenfunction:: gmtime(std::time_t time) -.. _variant-api: +.. _std-api: -Variant Formatting -================== +Standard Library Types Formatting +================================= -``fmt/variant.h`` provides formatters for +``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 @@ -470,10 +478,6 @@ Variant Formatting std::variant v1{}; // Prints "< >" -.. note:: - - Variant support is only available for C++17 and up. - .. _compile-api: Format string compilation diff --git a/include/fmt/std.h b/include/fmt/std.h index 84e4497a..e2041b8d 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,129 @@ 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++ = ' '; + 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, basic_string_view str) + -> OutputIt { + return write_escaped_string(out, str); +} + +// Returns true if T has a std::string-like interface, like std::string_view. +template class is_std_string_likevv { + template + static auto check(U* p) + -> decltype((void)p->find('a'), p->length(), (void)p->data(), int()); + template static void check(...); + + public: + static constexpr const bool value = + is_string::value || + std::is_convertible>::value || + !std::is_void(nullptr))>::value; +}; + +template +struct is_std_string_likevv> : std::true_type {}; + +template >::value)> +inline auto write_variant_alternative(OutputIt out, const T& str) -> OutputIt { + auto sv = std_string_view(str); + return write_variant_alternative(out, basic_string_view(sv)); +} + +template ::value)> +OutputIt write_variant_alternative(OutputIt out, const Arg v) { + return write_escaped_char(out, v); +} + +template ::type>::value && + !std::is_same::value)> +OutputIt write_variant_alternative(OutputIt out, const Arg& v) { + 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< + VariantT, 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 VariantT& value, FormatContext& ctx) const + -> decltype(ctx.out()) { + auto out = ctx.out(); + *out++ = '<'; + 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/include/fmt/variant.h b/include/fmt/variant.h deleted file mode 100644 index d82078b1..00000000 --- a/include/fmt/variant.h +++ /dev/null @@ -1,107 +0,0 @@ -// 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, - (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 7ae6f1b3..6cdb7ac4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -76,7 +76,6 @@ 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) add_fmt_test(unicode-test HEADER_ONLY) diff --git a/test/std-test.cc b/test/std-test.cc index 6589027f..0e34543c 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{}), " "); + 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'>"); + + 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), "< >"); + EXPECT_EQ(fmt::format("{}", v5), "<\"yes, this is variant\">"); +#endif +} diff --git a/test/variant-test.cc b/test/variant-test.cc deleted file mode 100644 index 4b358b49..00000000 --- a/test/variant-test.cc +++ /dev/null @@ -1,43 +0,0 @@ -// 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'>"); - - 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), "< >"); - EXPECT_EQ(fmt::format("{}", v5), "<\"yes, this is variant\">"); -} - -#endif // FMT_HAS_VARIANT