diff --git a/include/fmt/core.h b/include/fmt/core.h index 6611d5b897d5..4f9115b1bbcb 100644 --- a/include/fmt/core.h +++ b/include/fmt/core.h @@ -306,6 +306,24 @@ template using type_identity_t = typename type_identity::type; template using underlying_t = typename std::underlying_type::type; +template +struct disjunction : std::false_type {}; +template +struct disjunction

: P {}; +template +struct disjunction + : conditional_t> { +}; + +template +struct conjunction : std::true_type {}; +template +struct conjunction

: P {}; +template +struct conjunction + : conditional_t, P1> { +}; + struct monostate { constexpr monostate() {} }; diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h index c56199223d61..10429fc8eda3 100644 --- a/include/fmt/ranges.h +++ b/include/fmt/ranges.h @@ -246,7 +246,7 @@ template void for_each(Tuple&& tup, F&& f) { for_each(indexes, std::forward(tup), std::forward(f)); } -#if FMT_MSC_VERSION +#if FMT_MSC_VERSION && FMT_MSC_VERSION < 1920 // Older MSVC doesn't get the reference type correctly for arrays. template struct range_reference_type_impl { using type = decltype(*detail::range_begin(std::declval())); @@ -396,15 +396,18 @@ template struct formatter< R, Char, enable_if_t< - fmt::is_range::value -// Workaround a bug in MSVC 2019 and earlier. -#if !FMT_MSC_VERSION - && - (is_formattable>, - Char>::value || - detail::has_fallback_formatter< - detail::uncvref_type>, Char>::value) + conjunction +// Workaround a bug in MSVC 2017 and earlier. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 + , + disjunction< + is_formattable>, + Char>, + detail::has_fallback_formatter< + detail::uncvref_type>, Char> + > #endif + >::value >> { using range_type = detail::maybe_const_range; @@ -457,14 +460,20 @@ struct formatter< template struct formatter< T, Char, - enable_if_t::value -// Workaround a bug in MSVC 2019 and earlier. -#if !FMT_MSC_VERSION - && (is_formattable, Char>::value || - detail::has_fallback_formatter, Char>::value) - && (is_formattable, Char>::value || - detail::has_fallback_formatter, Char>::value) + enable_if_t +// Workaround a bug in MSVC 2017 and earlier. +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 + , + disjunction< + is_formattable, Char>, + detail::has_fallback_formatter, Char> + >, + disjunction< + is_formattable, Char>, + detail::has_fallback_formatter, Char> + > #endif + >::value >> { template FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) { diff --git a/include/fmt/std.h b/include/fmt/std.h index 41d2b2838b6d..227f4841123d 100644 --- a/include/fmt/std.h +++ b/include/fmt/std.h @@ -57,6 +57,10 @@ inline void write_escaped_path( } // namespace detail +#if !FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920 +// For MSVC 2017 and earlier using the partial specialization +// would cause an ambiguity error, therefore we provide it only +// conditionally. template struct formatter : formatter> { @@ -69,6 +73,7 @@ struct formatter basic_string_view(quoted.data(), quoted.size()), ctx); } }; +#endif FMT_END_NAMESPACE #endif diff --git a/test/std-test.cc b/test/std-test.cc index 02b5a591e092..fc8f72a0f0fb 100644 --- a/test/std-test.cc +++ b/test/std-test.cc @@ -6,13 +6,18 @@ // For the license information refer to format.h. #include "fmt/std.h" +#include "fmt/ranges.h" #include +#include #include "gtest/gtest.h" TEST(std_test, path) { -#ifdef __cpp_lib_filesystem +// Test ambiguity problem described in #2954. We need to exclude compilers +// where the ambiguity problem cannot be solved for now. +#if defined(__cpp_lib_filesystem) && \ + (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920) EXPECT_EQ(fmt::format("{:8}", std::filesystem::path("foo")), "\"foo\" "); EXPECT_EQ(fmt::format("{}", std::filesystem::path("foo\"bar.txt")), "\"foo\\\"bar.txt\""); @@ -31,6 +36,18 @@ TEST(std_test, path) { #endif } +TEST(ranges_std_test, format_vector_path) { +// Test ambiguity problem described in #2954. We need to exclude compilers +// where the ambiguity problem cannot be solved for now. +#if defined(__cpp_lib_filesystem) && \ + (!FMT_MSC_VERSION || FMT_MSC_VERSION >= 1920) + auto p = std::filesystem::path("foo/bar.txt"); + auto c = std::vector{"abc", "def"}; + EXPECT_EQ(fmt::format("path={}, range={}", p, c), + "path=\"foo/bar.txt\", range=[\"abc\", \"def\"]"); +#endif +} + TEST(std_test, thread_id) { EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty()); }