Fix is_formattable for tuple-like types.
<!--
Please read the contribution guidelines before submitting a pull request:
https://github.com/fmtlib/fmt/blob/master/CONTRIBUTING.md.
By submitting this pull request, you agree that your contributions are licensed
under the {fmt} license, and agree to future changes to the licensing.
-->
Fixes #2939.

Added a `is_tuple_formattable` helper value-trait to compute the formattability of the element-types of a tuple. Tried to make it look like `is_tuple_like` and `is_tuple_like_`. 

Made the `xchar_test.format_map` conditional. This test formats a map, whose value-type is `std::pair<const std::wstring,int>`. Because tuple-likes now also take the formattability of element-types into account, and because `is_formattable<std::wstring>` was false locally, this test no longer compiled. I was surprised that something that is not formattable could be formatted in the first place, and chose flight rather than fight.
{"label": "fmtlib:master", "ref": "master", "sha": "eaa8efb950be5d8f1803a99b06f76cf398c67cb8"}
tuple-formatter doesn't check that the tuple-elements are formattable:
https://github.com/fmtlib/fmt/blob/master/include/fmt/ranges.h#L285-L287

which makes `is_formattable<std::tuple<T>>::value` true:
https://godbolt.org/z/G3zsT4q4W

even when the `T` is not formattable, and trying to format `std::tuple<T>` will lead to a hard error.
tuple-formatter doesn't check that the tuple-elements are formattable.
diff --git a/include/fmt/ranges.h b/include/fmt/ranges.h
index a1fc80d2ae65..4f65eb4ba3df 100644
--- a/include/fmt/ranges.h
+++ b/include/fmt/ranges.h
@@ -202,6 +202,31 @@ template <size_t N>
 using make_index_sequence = make_integer_sequence<size_t, N>;
 #endif
 
+template <typename T>
+using tuple_index_sequence = make_index_sequence<std::tuple_size<T>::value>;
+
+template <typename T, typename C, bool = is_tuple_like_<T>::value>
+class is_tuple_formattable_ {
+ public:
+  static constexpr const bool value = false;
+};
+template <typename T, typename C> class is_tuple_formattable_<T, C, true> {
+  template <std::size_t... I>
+  static std::true_type check2(index_sequence<I...>,
+                               integer_sequence<bool, (I == I)...>);
+  static std::false_type check2(...);
+  template <std::size_t... I>
+  static decltype(check2(
+      index_sequence<I...>{},
+      integer_sequence<
+          bool, (is_formattable<typename std::tuple_element<I, T>::type,
+                                C>::value)...>{})) check(index_sequence<I...>);
+
+ public:
+  static constexpr const bool value =
+      decltype(check(tuple_index_sequence<T>{}))::value;
+};
+
 template <class Tuple, class F, size_t... Is>
 void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) noexcept {
   using std::get;
@@ -283,8 +308,15 @@ template <typename T> struct is_tuple_like {
       detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
 };
 
+template <typename T, typename C> struct is_tuple_formattable {
+  static constexpr const bool value =
+      detail::is_tuple_formattable_<T, C>::value;
+};
+
 template <typename TupleT, typename Char>
-struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
+struct formatter<TupleT, Char,
+                 enable_if_t<fmt::is_tuple_like<TupleT>::value &&
+                             fmt::is_tuple_formattable<TupleT, Char>::value>> {
  private:
   // C++11 generic lambda for format().
   template <typename FormatContext> struct format_each {

diff --git a/test/ranges-test.cc b/test/ranges-test.cc
index cdc6930dd3e9..468e4f40b39b 100644
--- a/test/ranges-test.cc
+++ b/test/ranges-test.cc
@@ -85,11 +85,22 @@ TEST(ranges_test, format_pair) {
   EXPECT_EQ(fmt::format("{}", p), "(42, 1.5)");
 }
 
+struct unformattable {};
+
 TEST(ranges_test, format_tuple) {
   auto t =
       std::tuple<int, float, std::string, char>(42, 1.5f, "this is tuple", 'i');
   EXPECT_EQ(fmt::format("{}", t), "(42, 1.5, \"this is tuple\", 'i')");
   EXPECT_EQ(fmt::format("{}", std::tuple<>()), "()");
+
+  EXPECT_TRUE((fmt::is_formattable<std::tuple<>>::value));
+  EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
+  EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable>>::value));
+  EXPECT_FALSE((fmt::is_formattable<std::tuple<unformattable, int>>::value));
+  EXPECT_FALSE((fmt::is_formattable<std::tuple<int, unformattable>>::value));
+  EXPECT_FALSE(
+      (fmt::is_formattable<std::tuple<unformattable, unformattable>>::value));
+  EXPECT_TRUE((fmt::is_formattable<std::tuple<int, float>>::value));
 }
 
 #ifdef FMT_RANGES_TEST_ENABLE_FORMAT_STRUCT
@@ -220,7 +231,6 @@ TEST(ranges_test, enum_range) {
 }
 
 #if !FMT_MSC_VERSION
-struct unformattable {};
 
 TEST(ranges_test, unformattable_range) {
   EXPECT_FALSE((fmt::has_formatter<std::vector<unformattable>,

{"ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"compile-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "ostream-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "std-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "core-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "scan-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "color-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "chrono-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "format-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "gtest-extra-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "args-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "compile-fp-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "unicode-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "assert-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "posix-mock-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "printf-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "enforce-checks-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "format-impl-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "os-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}, "xchar-test": {"run": "PASS", "test": "PASS", "fix": "PASS"}}
{"ranges-test": {"run": "PASS", "test": "NONE", "fix": "PASS"}}
{"passed_count": 20, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 19, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}
{"passed_count": 20, "failed_count": 0, "skipped_count": 0, "passed_tests": ["compile-test", "core-test", "chrono-test", "gtest-extra-test", "compile-fp-test", "unicode-test", "assert-test", "enforce-checks-test", "format-impl-test", "os-test", "xchar-test", "ostream-test", "ranges-test", "std-test", "scan-test", "color-test", "format-test", "args-test", "posix-mock-test", "printf-test"], "failed_tests": [], "skipped_tests": []}