Cleanup ostream interface

This commit is contained in:
Victor Zverovich
2021-09-03 09:33:24 -07:00
parent 20931baf1d
commit 5681563898
2 changed files with 31 additions and 42 deletions

View File

@ -30,56 +30,35 @@ template <class Char> class formatbuf : public std::basic_streambuf<Char> {
formatbuf(buffer<Char>& buf) : buffer_(buf) {} formatbuf(buffer<Char>& buf) : buffer_(buf) {}
protected: protected:
// The put-area is actually always empty. This makes the implementation // The put area is always empty. This makes the implementation simpler and has
// simpler and has the advantage that the streambuf and the buffer are always // the advantage that the streambuf and the buffer are always in sync and
// in sync and sputc never writes into uninitialized memory. The obvious // sputc never writes into uninitialized memory. A disadvantage is that each
// disadvantage is that each call to sputc always results in a (virtual) call // call to sputc always results in a (virtual) call to overflow. There is no
// to overflow. There is no disadvantage here for sputn since this always // disadvantage here for sputn since this always results in a call to xsputn.
// results in a call to xsputn.
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { auto overflow(int_type ch = traits_type::eof()) -> int_type FMT_OVERRIDE {
if (!traits_type::eq_int_type(ch, traits_type::eof())) if (!traits_type::eq_int_type(ch, traits_type::eof()))
buffer_.push_back(static_cast<Char>(ch)); buffer_.push_back(static_cast<Char>(ch));
return ch; return ch;
} }
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE { auto xsputn(const Char* s, std::streamsize count)
-> std::streamsize override {
buffer_.append(s, s + count); buffer_.append(s, s + count);
return count; return count;
} }
}; };
struct converter { // Checks if T has a user-defined operator<<.
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T); template <typename T, typename Char, typename Enable = void>
}; class is_streamable {
template <typename Char> struct test_stream : std::basic_ostream<Char> {
private:
void_t<> operator<<(converter);
};
// Hide insertion operators for built-in types.
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
template <typename Char, typename Traits>
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
template <typename Traits>
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
// Checks if T has a user-defined operator<< e.g. not a member of std::ostream.
template <typename T, typename Char> class is_streamable {
private: private:
template <typename U> template <typename U>
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>() static auto test(int)
<< std::declval<U>()), -> bool_constant<sizeof(std::declval<std::basic_ostream<Char>&>()
void_t<>>::value> << std::declval<U>()) != 0>;
test(int);
template <typename> static std::false_type test(...); template <typename> static auto test(...) -> std::false_type;
using result = decltype(test<T>(0)); using result = decltype(test<T>(0));
@ -89,10 +68,15 @@ template <typename T, typename Char> class is_streamable {
static const bool value = result::value; static const bool value = result::value;
}; };
// Formatting of arrays is intentionally disabled to prevent conflicts with // Formatting of built-in types and arrays is intentionally disabled because
// standard (non-ostream) formatters. // it's handled by standard (non-ostream) formatters.
template <typename T, size_t N, typename Char> template <typename T, typename Char>
struct is_streamable<T[N], Char> : std::false_type {}; struct is_streamable<
T, Char,
enable_if_t<std::is_arithmetic<T>::value || std::is_array<T>::value ||
std::is_same<T, char8_type>::value ||
(std::is_convertible<T, int>::value &&
!std::is_enum<T>::value)>> : std::false_type {};
// Write the content of buf to os. // Write the content of buf to os.
template <typename Char> template <typename Char>
@ -130,6 +114,8 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
-> decltype(ctx.begin()) { -> decltype(ctx.begin()) {
return formatter<basic_string_view<Char>, Char>::parse(ctx); return formatter<basic_string_view<Char>, Char>::parse(ctx);
} }
// DEPRECATED!
template <typename ParseCtx, template <typename ParseCtx,
FMT_ENABLE_IF(std::is_same< FMT_ENABLE_IF(std::is_same<
ParseCtx, basic_printf_parse_context<Char>>::value)> ParseCtx, basic_printf_parse_context<Char>>::value)>
@ -145,6 +131,8 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
basic_string_view<Char> str(buffer.data(), buffer.size()); basic_string_view<Char> str(buffer.data(), buffer.size());
return formatter<basic_string_view<Char>, Char>::format(str, ctx); return formatter<basic_string_view<Char>, Char>::format(str, ctx);
} }
// DEPRECATED!
template <typename OutputIt> template <typename OutputIt>
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx) auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
-> OutputIt { -> OutputIt {
@ -159,7 +147,7 @@ FMT_MODULE_EXPORT
template <typename Char> template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str, void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
basic_format_args<buffer_context<type_identity_t<Char>>> args) { basic_format_args<buffer_context<type_identity_t<Char>>> args) {
basic_memory_buffer<Char> buffer; auto buffer = basic_memory_buffer<Char>();
detail::vformat_to(buffer, format_str, args); detail::vformat_to(buffer, format_str, args);
detail::write_buffer(os, buffer); detail::write_buffer(os, buffer);
} }

View File

@ -258,7 +258,8 @@ std::ostream& operator<<(std::ostream& os, streamable_and_convertible_to_bool) {
} }
TEST(ostream_test, format_convertible_to_bool) { TEST(ostream_test, format_convertible_to_bool) {
EXPECT_EQ("foo", fmt::format("{}", streamable_and_convertible_to_bool())); // operator<< is intentionally not used because of potential ODR violations.
EXPECT_EQ(fmt::format("{}", streamable_and_convertible_to_bool()), "true");
} }
struct copyfmt_test {}; struct copyfmt_test {};