Skip to content

Commit 1e3dcbb

Browse files
DanielaEvitaut
authored andcommitted
fix: 'format_to_n' compiles 'std::back_inserter' arguments
std::back_insert_iterators model the OutputIterator concept but differ considerably in their traits and behavior. In particular the former made compilation to fail when format_to_n is given a back_inserter as first argument. The emulation of an OutputIterator is not perfect due to the behavioural differences of back_insert_iterators (e.g. assignment always implies increment) but good enough to be used within fmt's machinery.
1 parent f0328f8 commit 1e3dcbb

File tree

2 files changed

+67
-17
lines changed

2 files changed

+67
-17
lines changed

include/fmt/format.h

+55-17
Original file line numberDiff line numberDiff line change
@@ -738,35 +738,49 @@ class counting_iterator {
738738
T &operator*() const { return blackhole_; }
739739
};
740740

741+
template <typename OutputIt>
742+
class truncating_iterator_base {
743+
protected:
744+
OutputIt out_;
745+
std::size_t limit_;
746+
std::size_t count_;
747+
748+
truncating_iterator_base(OutputIt out, std::size_t limit)
749+
: out_(out), limit_(limit), count_(0) {}
750+
751+
public:
752+
typedef std::output_iterator_tag iterator_category;
753+
typedef void difference_type;
754+
typedef void pointer;
755+
typedef void reference;
756+
typedef truncating_iterator_base _Unchecked_type; // Mark iterator as checked.
757+
758+
OutputIt base() const { return out_; }
759+
std::size_t count() const { return count_; }
760+
};
761+
741762
// An output iterator that truncates the output and counts the number of objects
742763
// written to it.
764+
template <typename OutputIt, typename Enable = typename std::is_void<
765+
typename std::iterator_traits<OutputIt>::value_type>::type>
766+
class truncating_iterator;
767+
743768
template <typename OutputIt>
744-
class truncating_iterator {
745-
private:
769+
class truncating_iterator<OutputIt, std::false_type>:
770+
public truncating_iterator_base<OutputIt> {
746771
typedef std::iterator_traits<OutputIt> traits;
747772

748-
OutputIt out_;
749-
std::size_t limit_;
750-
std::size_t count_;
751773
mutable typename traits::value_type blackhole_;
752774

753775
public:
754-
typedef std::output_iterator_tag iterator_category;
755776
typedef typename traits::value_type value_type;
756-
typedef typename traits::difference_type difference_type;
757-
typedef typename traits::pointer pointer;
758-
typedef typename traits::reference reference;
759-
typedef truncating_iterator _Unchecked_type; // Mark iterator as checked.
760777

761778
truncating_iterator(OutputIt out, std::size_t limit)
762-
: out_(out), limit_(limit), count_(0) {}
763-
764-
OutputIt base() const { return out_; }
765-
std::size_t count() const { return count_; }
779+
: truncating_iterator_base<OutputIt>(out, limit) {}
766780

767781
truncating_iterator& operator++() {
768-
if (count_++ < limit_)
769-
++out_;
782+
if (this->count_++ < this->limit_)
783+
++this->out_;
770784
return *this;
771785
}
772786

@@ -776,7 +790,31 @@ class truncating_iterator {
776790
return it;
777791
}
778792

779-
reference operator*() const { return count_ < limit_ ? *out_ : blackhole_; }
793+
value_type& operator*() const {
794+
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
795+
}
796+
};
797+
798+
template <typename OutputIt>
799+
class truncating_iterator<OutputIt, std::true_type>:
800+
public truncating_iterator_base<OutputIt> {
801+
public:
802+
typedef typename OutputIt::container_type::value_type value_type;
803+
804+
truncating_iterator(OutputIt out, std::size_t limit)
805+
: truncating_iterator_base<OutputIt>(out, limit) {}
806+
807+
truncating_iterator& operator=(value_type val) {
808+
if (this->count_++ < this->limit_)
809+
this->out_ = val;
810+
return *this;
811+
}
812+
813+
truncating_iterator& operator++() { return *this; }
814+
815+
truncating_iterator& operator++(int) { return *this; }
816+
817+
truncating_iterator& operator*() { return *this; }
780818
};
781819

782820
// Returns true if value is negative, false otherwise.

test/format-test.cc

+12
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <cstring>
1313
#include <list>
1414
#include <memory>
15+
#include <string>
1516
#include <stdint.h>
1617

1718
// Check if fmt/format.h compiles with windows.h included before it.
@@ -185,6 +186,17 @@ TEST(IteratorTest, TruncatingIterator) {
185186
EXPECT_EQ(it.base(), p + 1);
186187
}
187188

189+
TEST(IteratorTest, TruncatingBackInserter) {
190+
std::string buffer;
191+
auto bi = std::back_inserter(buffer);
192+
fmt::internal::truncating_iterator<decltype(bi)> it(bi, 2);
193+
*it++ = '4';
194+
*it++ = '2';
195+
*it++ = '1';
196+
EXPECT_EQ(buffer.size(), 2);
197+
EXPECT_EQ(buffer, "42");
198+
}
199+
188200
TEST(MemoryBufferTest, Ctor) {
189201
basic_memory_buffer<char, 123> buffer;
190202
EXPECT_EQ(static_cast<size_t>(0), buffer.size());

0 commit comments

Comments
 (0)