C++ Generate Algorithms: std::fill and std::generate
Key takeaways
std::fill writes a single value, std::generate calls a function for each element, and std::iota writes a sequential series. This guide covers all three with working examples including proper C++11 random number generation.
Overview
The C++ standard library has a small family of algorithms for filling ranges with values:
| Algorithm | Header | Writes | Use when |
|---|---|---|---|
std::fill | <algorithm> | Same value to all | Resetting to zero, marking flags |
std::fill_n | <algorithm> | Same value to first N | Partial initialization |
std::generate | <algorithm> | Callable result per element | Computed or random values |
std::generate_n | <algorithm> | Callable for N elements | Appending generated data |
std::iota | <numeric> | Sequential increments | Index sequences, ranges |
std::fill
Writes the same value to every element in a range:
#include <algorithm>
#include <vector>
#include <array>
#include <iostream>
int main() {
// Fill entire vector
std::vector<int> v(8);
std::fill(v.begin(), v.end(), 42);
// v: {42, 42, 42, 42, 42, 42, 42, 42}
// Fill part of a vector
std::fill(v.begin() + 2, v.begin() + 5, 99);
// v: {42, 42, 99, 99, 99, 42, 42, 42}
// Works on any container
std::array<bool, 10> flags;
std::fill(flags.begin(), flags.end(), false);
// Works on C arrays too
int arr[5];
std::fill(arr, arr + 5, -1);
// arr: {-1, -1, -1, -1, -1}
}
std::fill_n
Fill exactly N elements starting at a position:
fill_n exists for the case where you don’t have (or don’t want to compute) an end iterator — it takes a starting position and a count instead of a [begin, end) range. This distinction matters most when the output iterator isn’t a regular random-access iterator with a meaningful “distance to end,” which is exactly the case with std::back_inserter: that iterator has no concept of an end at all, since it’s just a proxy that calls push_back on every assignment. Passing fill_n a count instead of a range sidesteps this entirely, which is why the append-to-an-empty-vector pattern below only works with fill_n, not plain fill.
std::vector<int> v(10, 0);
// Set first 5 elements to 1
std::fill_n(v.begin(), 5, 1);
// v: {1, 1, 1, 1, 1, 0, 0, 0, 0, 0}
// Append N elements to a vector (with back_inserter)
std::vector<int> result;
std::fill_n(std::back_inserter(result), 4, 7);
// result: {7, 7, 7, 7}
std::generate
Calls a callable for each element and writes the result. The callable takes no arguments and returns a value:
Unlike fill, which can only ever write the same value everywhere, generate delegates the actual value to whatever the callable produces — and because that callable is invoked fresh for every single element, it can carry state between calls via capture. This is what makes the three examples below possible with the exact same algorithm call: a stateful counter, a toggling boolean, and a running Fibonacci pair are all just different closures plugged into the same generate invocation. The callable’s signature (() -> value_type, no parameters) is deliberately minimal — generate doesn’t tell the callable which index or position it’s filling, so any position-dependent logic has to be tracked inside the callable’s own captured state, as the counter and Fibonacci examples do.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> v(5);
// Incrementing counter via stateful lambda
int counter = 0;
std::generate(v.begin(), v.end(), [&counter] {
return counter++;
});
// v: {0, 1, 2, 3, 4}
// Alternating values
bool toggle = false;
std::generate(v.begin(), v.end(), [&toggle] {
toggle = !toggle;
return toggle ? 1 : 0;
});
// v: {1, 0, 1, 0, 1}
// Fibonacci sequence
int a = 0, b = 1;
std::generate(v.begin(), v.end(), [&a, &b] {
int current = a;
int next = a + b;
a = b;
b = next;
return current;
});
// v: {0, 1, 1, 2, 3}
}
std::generate_n
Generate N elements and append to a container:
generate_n relates to generate exactly the way fill_n relates to fill — it swaps the [begin, end) range for a start-plus-count, which is precisely what’s needed to work with back_inserter and grow a container rather than overwrite an existing one. This combination (an empty vector, back_inserter, and generate_n) is the standard idiom for building up a container from scratch with computed values, as opposed to generate on an existing range, which only ever overwrites values that already have storage allocated.
#include <algorithm>
#include <vector>
#include <iterator>
int main() {
std::vector<int> v;
// Append 5 squares
int n = 0;
std::generate_n(std::back_inserter(v), 5, [&n] {
return n * n++; // 0, 1, 4, 9, 16
});
// v: {0, 1, 4, 9, 16}
}
std::iota
Fills a range with consecutively incremented values. Defined in <numeric>:
iota is really just a specialized, more expressive form of generate — it writes value, then value + 1, then value + 2, and so on, which you could reproduce with generate and a stateful lambda that increments a counter each call. The reason iota exists as its own algorithm rather than being left to generate + lambda is purely expressiveness and safety: std::iota(v.begin(), v.end(), 0) states its intent directly, works with any type supporting operator++ (not just integers — the char example below relies on this genericity), and avoids the small ceremony of writing out a capturing lambda for what is a very common, very simple pattern. Note that it lives in <numeric> rather than <algorithm>, which is a common source of “undeclared identifier” compile errors for people expecting it alongside fill and generate.
#include <numeric>
#include <vector>
#include <list>
#include <iostream>
int main() {
// Fill with 0, 1, 2, 3, 4
std::vector<int> v(5);
std::iota(v.begin(), v.end(), 0);
// v: {0, 1, 2, 3, 4}
// Start from a different value
std::vector<int> w(5);
std::iota(w.begin(), w.end(), 10);
// w: {10, 11, 12, 13, 14}
// Works with any incrementable type — including chars
std::vector<char> letters(5);
std::iota(letters.begin(), letters.end(), 'a');
// letters: {'a', 'b', 'c', 'd', 'e'}
// Build an index array for indirect sorting
std::vector<int> indices(10);
std::iota(indices.begin(), indices.end(), 0);
// indices: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
// then sort indices by comparing data[i]
}
Indirect Sorting with iota
A common pattern: sort an index array instead of the data, to get sorted order without moving elements:
This pattern earns its keep whenever the elements being “sorted” are expensive to move (large structs, or data that other code holds pointers/references into and can’t have relocated), or when you need more than one sorted view of the same underlying data at once — you can build a second indices array sorted by a different key without touching names at all, or affecting the first sorted view. iota supplies the raw material (0, 1, 2, ..., n-1) that sort’s comparator then reorders based on values it looks up indirectly through names[a]/names[b] — the sort algorithm itself has no idea it’s sorting indices rather than the actual strings; it just sees ints and a comparator that happens to consult an outside array.
#include <numeric>
#include <vector>
#include <algorithm>
#include <string>
#include <iostream>
int main() {
std::vector<std::string> names = {"Charlie", "Alice", "Bob", "Dave"};
// Build indices 0..n-1
std::vector<int> indices(names.size());
std::iota(indices.begin(), indices.end(), 0);
// Sort indices by name value
std::sort(indices.begin(), indices.end(),
[&names](int a, int b) { return names[a] < names[b]; });
// Print in sorted order without moving the original vector
for (int i : indices) {
std::cout << names[i] << '\n'; // Alice, Bob, Charlie, Dave
}
}
Random Number Generation with generate
Use C++11 <random> instead of rand():
#include <algorithm>
#include <random>
#include <vector>
#include <iostream>
int main() {
// Set up random engine and distribution
std::mt19937 engine(std::random_device{}()); // Mersenne Twister, seeded
std::uniform_int_distribution<int> dist(1, 100); // integers in [1, 100]
// Fill vector with random values
std::vector<int> v(10);
std::generate(v.begin(), v.end(), [&engine, &dist] {
return dist(engine);
});
for (int x : v) std::cout << x << ' ';
// Different distribution: normal (bell curve)
std::normal_distribution<double> normal(0.0, 1.0); // mean=0, stddev=1
std::vector<double> samples(1000);
std::generate(samples.begin(), samples.end(), [&engine, &normal] {
return normal(engine);
});
}
Why not rand()?
- Poor statistical quality (short period, bad distribution)
- Shared global state — not thread-safe
- No control over distribution
<random>gives you proper distributions and thread-local engines
Functor-Based Generator
When a lambda captures too many variables, a functor class is cleaner:
A lambda with two or three captured variables and a multi-line body starts to read worse than an equivalent named class, precisely because a lambda’s whole appeal is being terse and local — once it stops being either, you’re paying the syntax cost of a lambda without getting the readability benefit. A functor class like IdGenerator makes the generator’s state (next_id_, step_) explicit named members instead of anonymous captures, gives the whole generator a name that documents its purpose at the call site (IdGenerator(1000, 10) reads clearly; a five-line lambda inline does not), and — because it’s an ordinary class — can be unit-tested, reused across multiple generate calls, or extended with additional methods in a way an anonymous lambda cannot.
#include <algorithm>
#include <vector>
class IdGenerator {
int next_id_;
int step_;
public:
IdGenerator(int start = 1000, int step = 10)
: next_id_(start), step_(step) {}
int operator()() {
int id = next_id_;
next_id_ += step_;
return id;
}
};
int main() {
std::vector<int> ids(5);
std::generate(ids.begin(), ids.end(), IdGenerator(1000, 10));
// ids: {1000, 1010, 1020, 1030, 1040}
}
Common Pitfalls
Empty container — nothing happens:
Every algorithm covered in this guide (fill, fill_n on an iterator without an inserter, generate, iota) only ever writes to existing storage — none of them allocate space or grow the container. std::fill(v.begin(), v.end(), 42) on an empty vector is iterating over a range where begin() == end(), which is a well-defined empty range, so the loop body simply never executes; it’s not an error, it’s just a no-op that silently does nothing. This trips people up specifically because the code looks like it should populate the vector, and the absence of any error message makes the bug easy to miss until the vector turns up empty somewhere downstream.
std::vector<int> v; // size 0
std::fill(v.begin(), v.end(), 42); // no-op — empty range
// Fix: resize first
v.resize(5);
std::fill(v.begin(), v.end(), 42); // now works
// Or use fill_n with back_inserter:
std::fill_n(std::back_inserter(v), 5, 42);
Wrong capture in generate — lambda doesn’t update outer state:
This is actually a compile error rather than a silent bug, and it’s worth understanding exactly why. [=] captures counter by value, and a lambda’s operator() is const by default — which means the captured copy is treated as const inside the body, so counter++ (a mutation) fails to compile with something like “cannot assign to a variable captured by copy in a non-mutable lambda.” You’d need to add mutable to make [=] () mutable { return counter++; } compile at all, and even then it wouldn’t do what you want: the mutable copy lives inside the lambda object itself, so it would increment across successive calls (0, 1, 2, 3, 4…) since generate reuses the same lambda instance for every element, but the outer counter would remain untouched at 0 the whole time, because it was only ever copied once at the moment the lambda was created. [&] sidesteps both problems: capturing by reference means counter++ mutates the exact same variable the outer scope sees, no mutable is needed since you’re not modifying the captured reference itself, and the outer counter reflects every increment both during and after the generate call.
int counter = 0;
// Wrong: [=] alone won't even compile — counter++ mutates a const-by-default capture
// std::generate(v.begin(), v.end(), [=] { return counter++; });
// Correct: [&] captures by reference
std::generate(v.begin(), v.end(), [&] { return counter++; });
// Elements get 0, 1, 2, 3, 4 ... and the outer counter ends at 5
fill is faster than generate for constants:
generate has no way of knowing the callable returns the same value every time — it has to call it once per element, unconditionally, because in general the callable’s return value can depend on hidden state (as every example earlier in this guide demonstrates). fill, by contrast, is given the value directly, so for trivial types the standard library implementation is free to recognize the pattern and lower it to a single memset-style bulk write instead of a per-element assignment loop — a difference that’s invisible for a five-element vector but measurable once you’re filling millions of elements. The practical rule is simple: if the value truly never changes, reach for fill; only use generate when the value actually needs to be computed per position.
// Slow: lambda call overhead per element
std::generate(v.begin(), v.end(), [] { return 42; });
// Fast: optimized to memset-like operation for trivial types
std::fill(v.begin(), v.end(), 42);
Choosing between fill, generate and iota
std::fill— same value everywhere; often optimizes tomemsetfor trivial typesstd::fill_n— same value for first N elements; works withback_inserterto appendstd::generate— calls a callable per element; use[&]capture for stateful generatorsstd::iota— sequential values with++; in<numeric>, not<algorithm>- Use
<random>withstd::mt19937and appropriate distributions — neverrand() - Resize or use
back_inserterbefore fill/generate — they don’t add elements, only write to existing positions