diff --git a/include/fmt/base.h b/include/fmt/base.h index e8dedc4a..671c2ec7 100644 --- a/include/fmt/base.h +++ b/include/fmt/base.h @@ -1069,6 +1069,24 @@ template class iterator_buffer : public buffer { auto out() -> T* { return &*this->end(); } }; +template +class container_buffer : public buffer { + private: + using value_type = typename Container::value_type; + + static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { + auto& self = static_cast(buf); + self.container.resize(capacity); + self.set(&self.container[0], capacity); + } + + public: + Container& container; + + explicit container_buffer(Container& c) + : buffer(grow, c.size()), container(c) {} +}; + // A buffer that writes to a container with the contiguous storage. template class iterator_buffer< @@ -1076,25 +1094,16 @@ class iterator_buffer< enable_if_t::value && is_contiguous::value, typename OutputIt::container_type::value_type>> - : public buffer { + : public container_buffer { private: - using container_type = typename OutputIt::container_type; - using value_type = typename container_type::value_type; - container_type& container_; - - static FMT_CONSTEXPR void grow(buffer& buf, size_t capacity) { - auto& self = static_cast(buf); - self.container_.resize(capacity); - self.set(&self.container_[0], capacity); - } + using base = container_buffer; public: - explicit iterator_buffer(container_type& c) - : buffer(grow, c.size()), container_(c) {} + explicit iterator_buffer(typename OutputIt::container_type& c) : base(c) {} explicit iterator_buffer(OutputIt out, size_t = 0) - : iterator_buffer(get_container(out)) {} + : base(get_container(out)) {} - auto out() -> OutputIt { return OutputIt(container_); } + auto out() -> OutputIt { return OutputIt(this->container); } }; // A buffer that counts the number of code units written discarding the output. diff --git a/include/fmt/format.h b/include/fmt/format.h index b0bb6024..622a2042 100644 --- a/include/fmt/format.h +++ b/include/fmt/format.h @@ -938,6 +938,41 @@ class basic_memory_buffer : public detail::buffer { using memory_buffer = basic_memory_buffer; +// A writer to a buffered stream. It doesn't own the underlying stream. +class writer { + private: + detail::buffer* buf_; + + // We cannot create a file buffer in advance because any write to a FILE may + // invalidate it. + FILE* file_; + + public: + writer(FILE* f) : buf_(nullptr), file_(f) {} + writer(detail::buffer& buf) : buf_(&buf) {} + + /// Formats `args` according to specifications in `fmt` and writes the + /// output to the file. + template void print(format_string fmt, T&&... args) { + if (buf_) + fmt::format_to(appender(*buf_), fmt, std::forward(args)...); + else + fmt::print(file_, fmt, std::forward(args)...); + } +}; + +class string_buffer { + private: + std::string str_; + detail::container_buffer buf_; + + public: + string_buffer() : buf_(str_) {} + + operator writer() { return buf_; } + std::string& str() { return str_; } +}; + template struct is_contiguous> : std::true_type { }; diff --git a/include/fmt/os.h b/include/fmt/os.h index cfc9157c..218721b9 100644 --- a/include/fmt/os.h +++ b/include/fmt/os.h @@ -376,6 +376,11 @@ class FMT_API ostream : private detail::buffer { ostream(ostream&& other) noexcept; ~ostream(); + operator writer() { + detail::buffer& buf = *this; + return buf; + } + void flush() { if (size() == 0) return; file_.write(data(), size() * sizeof(data()[0])); diff --git a/test/format-test.cc b/test/format-test.cc index 077109c6..e08f6a28 100644 --- a/test/format-test.cc +++ b/test/format-test.cc @@ -2478,3 +2478,27 @@ FMT_END_NAMESPACE TEST(format_test, ustring) { EXPECT_EQ(fmt::format("{}", ustring()), "ustring"); } + +TEST(format_test, writer) { + auto write_to_stdout = []() { + auto w = fmt::writer(stdout); + w.print("{}", 42); + }; + EXPECT_WRITE(stdout, write_to_stdout(), "42"); + +#if FMT_USE_FCNTL + auto pipe = fmt::pipe(); + auto write_end = pipe.write_end.fdopen("w"); + fmt::writer(write_end.get()).print("42"); + write_end.close(); + auto read_end = pipe.read_end.fdopen("r"); + int n = 0; + int result = fscanf(read_end.get(), "%d", &n); + (void)result; + EXPECT_EQ(n, 42); +#endif + + auto s = fmt::string_buffer(); + fmt::writer(s).print("foo"); + EXPECT_EQ(s.str(), "foo"); +}