Skip to content

Commit

Permalink
Improve handling of back_insert_iterator that writes into a buffer
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Jul 13, 2024
1 parent 6a192f8 commit 33e7ed1
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 3 deletions.
31 changes: 29 additions & 2 deletions include/fmt/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -474,15 +474,24 @@ FMT_CONSTEXPR auto compare(const Char* s1, const Char* s2, std::size_t n)
return 0;
}

namespace detect {

using namespace std;

template <typename It, typename Enable = std::true_type>
struct is_back_insert_iterator : std::false_type {};

template <typename It>
struct is_back_insert_iterator<
It,
bool_constant<std::is_same<
decltype(back_inserter(std::declval<typename It::container_type&>())),
It>::value>> : std::true_type {};

} // namespace detect

using detect::is_back_insert_iterator;

// Extracts a reference to the container from *insert_iterator.
template <typename OutputIt>
inline auto get_container(OutputIt it) -> typename OutputIt::container_type& {
Expand Down Expand Up @@ -1158,6 +1167,7 @@ template <typename T> class basic_appender {
using difference_type = ptrdiff_t;
using pointer = T*;
using reference = T&;
using container_type = detail::buffer<T>;
FMT_UNCHECKED_ITERATOR(basic_appender);

FMT_CONSTEXPR basic_appender(detail::buffer<T>& buf) : buffer_(&buf) {}
Expand All @@ -1174,6 +1184,10 @@ template <typename T> class basic_appender {
using appender = basic_appender<char>;

namespace detail {
namespace detect {
template <typename T>
struct is_back_insert_iterator<basic_appender<T>> : std::true_type {};
}

template <typename T, typename Enable = void>
struct locking : std::true_type {};
Expand Down Expand Up @@ -1239,12 +1253,25 @@ constexpr auto has_const_formatter() -> bool {
return has_const_formatter_impl<Context>(static_cast<T*>(nullptr));
}

template <typename It, typename Enable = std::true_type>
struct is_buffer_appender : std::false_type {};
template <typename It>
struct is_buffer_appender<
It, bool_constant<
is_back_insert_iterator<It>::value &&
std::is_base_of<buffer<typename It::container_type::value_type>,
typename It::container_type>::value>>
: std::true_type {};

// Maps an output iterator to a buffer.
template <typename T, typename OutputIt>
template <typename T, typename OutputIt,
FMT_ENABLE_IF(!is_buffer_appender<OutputIt>::value)>
auto get_buffer(OutputIt out) -> iterator_buffer<OutputIt, T> {
return iterator_buffer<OutputIt, T>(out);
}
template <typename T> auto get_buffer(basic_appender<T> out) -> buffer<T>& {
template <typename T, typename OutputIt,
FMT_ENABLE_IF(is_buffer_appender<OutputIt>::value)>
auto get_buffer(OutputIt out) -> buffer<T>& {
return get_container(out);
}

Expand Down
12 changes: 11 additions & 1 deletion test/base-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ TEST(base_test, is_back_insert_iterator) {

TEST(base_test, buffer_appender) {
#ifdef __cpp_lib_ranges
static_assert(std::output_iterator<fmt::appender, char>);
EXPECT_TRUE((std::output_iterator<fmt::appender, char>));
#endif
}

Expand Down Expand Up @@ -268,6 +268,16 @@ TEST(buffer_test, append_allocates_enough_storage) {
buffer.append(test, test + 9);
}

TEST(base_test, get_buffer) {
mock_buffer<char> buffer;
void* buffer_ptr = &buffer;
auto&& appender_result = fmt::detail::get_buffer<char>(fmt::appender(buffer));
EXPECT_EQ(&appender_result, buffer_ptr);
auto&& back_inserter_result =
fmt::detail::get_buffer<char>(std::back_inserter(buffer));
EXPECT_EQ(&back_inserter_result, buffer_ptr);
}

struct custom_context {
using char_type = char;
using parse_context_type = fmt::format_parse_context;
Expand Down

0 comments on commit 33e7ed1

Please sign in to comment.