Don't use stringstream
diff --git a/include/fmt/format-inl.h b/include/fmt/format-inl.h
index 9ffcc83..80b44a4 100644
--- a/include/fmt/format-inl.h
+++ b/include/fmt/format-inl.h
@@ -17,7 +17,6 @@
 #include <cstring>  // std::memmove
 #include <cwchar>
 #include <exception>
-#include <sstream>
 
 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
 #  include <locale>
@@ -119,20 +118,18 @@
 }
 #endif
 
-FMT_FUNC auto write_int(unsigned long long value, const format_specs& specs,
-                        locale_ref loc) -> std::string {
+FMT_FUNC auto write_int(appender out, unsigned long long value,
+                        const format_specs& specs, locale_ref loc) -> bool {
 #ifndef FMT_STATIC_THOUSANDS_SEPARATOR
   auto locale = loc.get<std::locale>();
   // We cannot use the num_put<char> facet because it may produce output in
   // a wrong encoding.
   if (!std::has_facet<num_format_facet<std::locale>>(locale)) return {};
-  auto&& buf = std::basic_stringbuf<char>();
-  auto out = std::ostreambuf_iterator<char>(&buf);
   std::use_facet<num_format_facet<std::locale>>(locale).put(out, value, specs,
                                                             locale);
-  return buf.str();
+  return true;
 #endif
-  return {};
+  return false;
 }
 
 }  // namespace detail
diff --git a/include/fmt/format.h b/include/fmt/format.h
index d31d124..a792f31 100644
--- a/include/fmt/format.h
+++ b/include/fmt/format.h
@@ -2014,33 +2014,35 @@
       });
 }
 
-FMT_API auto write_int(unsigned long long value, const format_specs& specs,
-                       locale_ref loc) -> std::string;
-template <typename Char>
-inline auto write_int(unsigned long long, const basic_format_specs<Char>&,
-                      locale_ref) -> std::string {
-  return {};
+FMT_API auto write_int(appender out, unsigned long long value,
+                       const format_specs& specs, locale_ref loc) -> bool;
+template <typename OutputIt, typename Char>
+inline auto write_int(OutputIt, unsigned long long,
+                      const basic_format_specs<Char>&, locale_ref) -> bool {
+  return false;
 }
 
 template <typename OutputIt, typename UInt, typename Char>
 auto write_int(OutputIt& out, UInt value, unsigned prefix,
                const basic_format_specs<Char>& specs, locale_ref loc) -> bool {
-  auto str = std::string();
+  auto result = false;
+  auto buf = memory_buffer();
   if (sizeof(value) <= sizeof(unsigned long long))
-    str = write_int(static_cast<unsigned long long>(value), specs, loc);
-  if (str.empty()) {
+    result = write_int(appender(buf), static_cast<unsigned long long>(value),
+                       specs, loc);
+  if (!result) {
     auto grouping = digit_grouping<Char>(loc);
     out = write_int(out, value, prefix, specs, grouping);
     return true;
   }
-  size_t size = to_unsigned((prefix != 0 ? 1 : 0) + str.size());
+  size_t size = to_unsigned((prefix != 0 ? 1 : 0) + buf.size());
   out = write_padded<align::right>(
       out, specs, size, size, [&](reserve_iterator<OutputIt> it) {
         if (prefix != 0) {
           char sign = static_cast<char>(prefix);
           *it++ = static_cast<Char>(sign);
         }
-        return copy_str<Char>(str.data(), str.data() + str.size(), it);
+        return copy_str<Char>(buf.data(), buf.data() + buf.size(), it);
       });
   return true;
 }
@@ -4180,17 +4182,14 @@
  public:
   static FMT_API typename Locale::id id;
 
-  using iter_type = std::ostreambuf_iterator<char>;
-
-  auto put(iter_type out, unsigned long long val, const format_specs& specs,
-           Locale& loc) const -> iter_type {
-    return do_put(out, val, specs, loc);
+  void put(appender out, unsigned long long val, const format_specs& specs,
+           Locale& loc) const {
+    do_put(out, val, specs, loc);
   }
 
  protected:
-  virtual auto do_put(iter_type out, unsigned long long val,
-                      const format_specs& specs, Locale& loc) const
-      -> iter_type = 0;
+  virtual void do_put(appender out, unsigned long long val,
+                      const format_specs& specs, Locale& loc) const = 0;
 };
 
 #if FMT_USE_USER_DEFINED_LITERALS
diff --git a/test/xchar-test.cc b/test/xchar-test.cc
index e195831..91300da 100644
--- a/test/xchar-test.cc
+++ b/test/xchar-test.cc
@@ -522,20 +522,18 @@
 
 class num_format : public fmt::num_format_facet<std::locale> {
  protected:
-  iter_type do_put(iter_type out, unsigned long long, const fmt::format_specs&,
-                   std::locale&) const override;
+  void do_put(fmt::appender out, unsigned long long, const fmt::format_specs&,
+              std::locale&) const override;
 };
 
-num_format::iter_type num_format::do_put(iter_type out, unsigned long long,
-                                         const fmt::format_specs&,
-                                         std::locale&) const {
-  const char s[] = "foo";
-  return std::copy_n(s, sizeof(s) - 1, out);
+void num_format::do_put(fmt::appender out, unsigned long long value,
+                        const fmt::format_specs&, std::locale&) const {
+  fmt::format_to(out, "[{}]", value);
 }
 
 TEST(locale_test, num_format) {
   auto loc = std::locale(std::locale(), new num_format());
-  EXPECT_EQ(fmt::format(loc, "{:L}", 42), "foo");
+  EXPECT_EQ(fmt::format(loc, "{:L}", 42), "[42]");
 }
 
 #endif  // FMT_STATIC_THOUSANDS_SEPARATOR