Enable inheriting from formatter<std::string_view>
Fixes #4036

This formatter specialization with `base::format` means a class implicitly convertible to `std::string_view` will now be converted by this format function before being passed to the `fmt::string_view` format function.
This wouldn't work previously as the compiler may only perform one implicit conversion, and we need 2 here (from our type, to `std::string_view`, to `fmt::string_view`).

{"label": "fmtlib:master", "ref": "master", "sha": "1408f1824d6a23643e178ee6e46478fb550a0963"}
libfmt 10.2 / gcc 13

I have a string-like class implicitly-convertible to std::string_view. Based on older documentation I'd expect that to Just Work in `fmt::format()`but I see from other issues that matching on implicit conversion has been disabled.  Annoying, but fair enough.

When I create a `fmt::formatter` specialisation for my class that simply inherits `fmt::formatter<string_view>`, this does not work, `fmt::format` still considers S to be an un-formattable type.
```
template<>
class fmt::formatter<S> 
    : public fmt::formatter<std::string_view> {};
```
https://godbolt.org/z/aMqqWaso9

The equivalent code using std::formatter works ok: https://godbolt.org/z/T7zzKzzf3

formatter simply inheriting from built-in formatter is not recognised
diff --git a/include/fmt/format.h b/include/fmt/format.h
index 7c2a19b4084d..41bace093249 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -3978,9 +3978,15 @@ struct formatter<T, Char, enable_if_t<detail::has_format_as<T>::value>>
   }
 };
 
-#define FMT_FORMAT_AS(Type, Base) \
-  template <typename Char>        \
-  struct formatter<Type, Char> : formatter<Base, Char> {}
+#define FMT_FORMAT_AS(Type, Base)                                              \
+  template <typename Char>                                                     \
+  struct formatter<Type, Char> : formatter<Base, Char> {                       \
+    template <typename FormatContext>                                          \
+    auto format(Type value, FormatContext& ctx) const -> decltype(ctx.out()) { \
+      using base = formatter<Base, Char>;                                      \
+      return base::format(value, ctx);                                         \
+    }                                                                          \
+  }
 
 FMT_FORMAT_AS(signed char, int);
 FMT_FORMAT_AS(unsigned char, unsigned);

diff --git a/test/format-test.cc b/test/format-test.cc
index a9ef19fc19c8..b16f11cc8430 100644
--- a/test/format-test.cc
+++ b/test/format-test.cc
@@ -1650,6 +1650,20 @@ TEST(format_test, format_explicitly_convertible_to_std_string_view) {
   EXPECT_EQ("'foo'",
             fmt::format("{}", explicitly_convertible_to_std_string_view()));
 }
+
+struct convertible_to_std_string_view {
+  operator std::string_view() const noexcept { return "Hi there"; }
+};
+FMT_BEGIN_NAMESPACE
+template <>
+class formatter<convertible_to_std_string_view>
+    : public formatter<std::string_view> {};
+FMT_END_NAMESPACE
+
+TEST(format_test, format_implicitly_convertible_and_inherits_string_view) {
+  static_assert(fmt::is_formattable<convertible_to_std_string_view>{}, "");
+  EXPECT_EQ("Hi there", fmt::format("{}", convertible_to_std_string_view{}));
+}
 #endif
 
 class Answer {};

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