A C++23 solution, for fun: https://godbolt.org/z/doGvozasT
#include <fmt/ranges.h>
#include <ranges>
#include <span>
#include <string_view>
auto chunk(std::span<const std::string_view> strings, size_t bufsize) {
auto pred = [bufsize, size{0uz}](const auto &a, const auto &b) mutable {
size += a.size(); // Keep track of the size of the current chunk.
if (size + b.size() > bufsize) { // If adding b would exceed the buffer
size = 0; // capacity, reset the size and
return false; // split the array between a and b.
}
return true; // Don't split otherwise.
};
return std::views::chunk_by(std::move(strings), pred);
}
constexpr std::string_view strings[]{"test", "foo", "bar", "baz", "glork", "Another test", "overflow", "packets", "datagrams"};
constexpr size_t bufsize = 19; // All sizes exclude null terminator
int main() {
for (auto &&chunk : chunk(strings, bufsize))
fmt::println("{}", chunk);
}
["test", "foo", "bar", "baz", "glork"]
["Another test"]
["overflow", "packets"]
["datagrams"]
It has the advantage that all chunks and their elements are lazily evaluated, so no copies are made, and no dynamic allocation takes place.
A similar idea in Python:
from itertools import groupby
class Chunker:
def __init__(self, max_size):
self.max_size = max_size
self._size = 0
self._count = 0
def __call__(self, x):
this_size = len(x)
self._size += this_size
if self._size >= self.max_size:
self._count += 1
self._size = this_size
return self._count
for _, chunk in groupby(strings, Chunker(19)):
print(list(chunk))
['test', 'foo', 'bar', 'baz', 'glork']
['Another test']
['overflow', 'packets']
['datagrams']
You could also use a generator coroutine:
def chunk_by_size(strings: list[str], max_size: int):
buffer = ""
for s in strings:
if len(buffer) + len(s) <= max_size:
buffer += s
else:
yield buffer
buffer = s
yield buffer
for chunk in chunk_by_size(strings, 19):
print(chunk)
testfoobarbazglork
Another test
overflowpackets
datagrams
In C++, range-based for loops are simply syntactic sugar for standard for loops with explicit iterators, see Range-based for loop (since C++11) - cppreference.com.
In C++ 11, the following loop
for (auto element : range)
foo(element);
is roughly (see link above for the exact equivalence) equivalent to
auto &&__range = range;
for (auto __it = begin(__range), __end = end(__range); __it != __end; ++__it) {
auto element = *__it;
foo(element);
}
If you wanted to, you could write this out yourself, and then you have access to the iterator __it in the loop body, where you could use std::advance(__it, n) or ++__it whenever you want.
That being said, I believe this is bad practice, because it is very hard to read (in all languages, not just in C++).
Instead, try to express your intent more clearly by using one of the standard library algorithms or views. That makes it much easier to see what's going on compared to raw loops, especially ones that manipulate their own iterators or indices all over the place.