Skip to main content

Output iterators and format_to

fmt::format allocates a std::string that you often immediately throw away — append it to something, write it to a socket, discard everything but its length. format_to writes straight into whatever destination you already have, skipping that throwaway allocation.

The signature

template <typename OutputIt, typename... T>
OutputIt format_to(OutputIt out, format_string<T...> fmt, T&&... args);

It returns the iterator advanced past the last character written — useful for chaining several format_to calls into the same destination.

Appending to a string

std::string s;
fmt::format_to(std::back_inserter(s), "{}", x);

std::back_inserter is the most common destination: fmt appends through it exactly like any other output iterator, so s grows the same way push_back would grow it.

std::string msg;
fmt::format_to(std::back_inserter(msg), "[{}] ", timestamp);
fmt::format_to(std::back_inserter(msg), "{}: ", level);
fmt::format_to(std::back_inserter(msg), "{}\n", body);

Writing into a fixed array

fmt::format_to_n bounds the write to a fixed-size buffer and reports how much would have been written, so you can detect truncation.

fixed_buffer.cpp
char buf[64];
auto result = fmt::format_to_n(buf, sizeof(buf) - 1, "{}: {}", key, value);
size_t written = std::min(result.size, sizeof(buf) - 1);
buf[written] = '\0'; // format_to_n never does this for you
if (result.size > sizeof(buf) - 1) {
// the message was truncated — result.size is the length it needed
}
format_to_n does not null-terminate — write the terminator yourself

format_to_n treats the buffer as a plain character range, not a C string. Forgetting the manual null terminator (as in the buffer size reserved above) is a classic off-by-one into undefined behavior on whatever reads the buffer next.

Sizing first

fmt::formatted_size measures the output length without producing it, letting you allocate exactly once instead of letting a growing buffer reallocate as it goes.

size_t n = fmt::formatted_size("{}: {}", key, value);
std::string s(n, '\0');
fmt::format_to(s.data(), "{}: {}", key, value);

Any output iterator

format_to works with anything satisfying the output iterator concept: std::back_inserter on a std::vector<char>, std::ostream_iterator<char>, or a custom iterator whose operator* and operator++ write to a socket, a ring buffer, or a memory-mapped file.

Comparison table

formatformat_toformat_to_nprint
AllocationAlways (new string)Depends on the iteratorNeverNever
BoundsUnboundedUnbounded (destination's responsibility)Bounded, reports overflowUnbounded
Return valueThe std::stringThe output iteratorIterator + would-be sizevoid
Typical useOne-off string buildingAppending into an existing buffer/containerFixed-size buffers, no allocation allowedDirect output to a stream

See also