Modifying Algorithms — copy, move, transform, replace, Erase-Remove Idiom
In this tutorial, you will learn about Modifying Algorithms. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ modifying algorithms mutate ranges through copy, move, transform, replace, fill, generate, and remove operations, with the erase-remove idiom permanently deleting elements.
What You'll Learn
You will copy and move data between containers with std::copy and std::move, apply functions element-wise with std::transform, replace or conditionally replace elements with std::replace and std::replace_if, fill and generate sequences with std::fill, std::generate, and std::iota, and permanently delete elements using the erase-remove idiom.
Why It Matters
Modifying algorithms replace manual loops for common data manipulation tasks. They Express intent clearly, avoid off-by-one errors, and are often optimized using SIMD instructions or memcpy. The erase-remove idiom in particular eliminates an entire class of bugs related to iterating while modifying.
Learning Path
graph LR
A["37: Sorting & Searching"] --> B["38: Modifying Algorithms"]
B --> C["39: Numeric Algorithms"]
C --> D["40: Ranges Library"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
std::copy and std::copy_if
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> source = {1, 2, 3, 4, 5};
std::vector<int> dest;
// copy from source to back of dest
std::copy(source.begin(), source.end(), std::back_inserter(dest));
// copy_if (conditional)
std::vector<int> evens;
std::copy_if(source.begin(), source.end(),
std::back_inserter(evens),
[](int x) { return x % 2 == 0; });
// copy_n (first n elements)
std::vector<int> first3;
std::copy_n(source.begin(), 3, std::back_inserter(first3));
// Raw array copy
int arr[5];
std::copy(source.begin(), source.end(), arr);
for (int x : dest) std::cout << x << " ";
std::cout << "\n";
}
std::move (Algorithm)
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <memory>
int main() {
std::vector<std::unique_ptr<int>> source;
source.push_back(std::make_unique<int>(10));
source.push_back(std::make_unique<int>(20));
source.push_back(std::make_unique<int>(30));
std::vector<std::unique_ptr<int>> dest;
// Move elements from source to dest
std::move(source.begin(), source.end(), std::back_inserter(dest));
// source elements are now in moved-from state (nullptr)
std::cout << "source[0] is null: " << (source[0] == nullptr) << "\n";
std::cout << "dest[0]: " << *dest[0] << "\n";
}
std::transform
#include <iostream>
#include <vector>
#include <algorithm>
#include <cctype>
#include <string>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// Unary transform: apply function to each element
std::vector<int> squares;
std::transform(nums.begin(), nums.end(),
std::back_inserter(squares),
[](int x) { return x * x; });
// Binary transform: combine two ranges
std::vector<int> a = {1, 2, 3};
std::vector<int> b = {4, 5, 6};
std::vector<int> sum;
std::transform(a.begin(), a.end(), b.begin(),
std::back_inserter(sum),
[](int x, int y) { return x + y; });
// Transform in place
std::transform(nums.begin(), nums.end(), nums.begin(),
[](int x) { return x * 10; });
// String transformation
std::string s = "hello";
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::toupper(c); });
std::cout << s << "\n"; // HELLO
}
std::replace and std::replace_if
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 3, 2, 4, 2, 5};
// Replace all 2s with 99
std::replace(v.begin(), v.end(), 2, 99);
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 1 99 3 99 4 99 5
// Conditional replace
std::replace_if(v.begin(), v.end(),
[](int x) { return x % 2 == 0; }, 0);
// Copy versions don't modify original
std::vector<int> original = {1, 2, 3, 2, 4};
std::vector<int> result;
std::replace_copy(original.begin(), original.end(),
std::back_inserter(result), 2, 99);
// original unchanged, result has replacements
}
std::fill, std::generate, std::iota
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
int main() {
std::vector<int> v(10);
// fill with constant
std::fill(v.begin(), v.begin() + 3, -1);
// fill_n (fill first n elements)
std::fill_n(v.begin(), 3, -2);
// generate with function
int n = 0;
std::generate(v.begin(), v.end(), [&n]() { return n++; });
// v now contains 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
// iota: fill with incrementing values
std::iota(v.begin(), v.end(), 100);
// v now contains 100, 101, 102, ...
for (int x : v) std::cout << x << " ";
std::cout << "\n";
}
The Erase-Remove Idiom
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 3, 2, 4, 2, 5, 2, 6};
// Step 1: remove shifts non-removed elements to the front
// Step 2: erase the "tail" of removed elements
// Remove all 2s
auto newEnd = std::remove(v.begin(), v.end(), 2);
v.erase(newEnd, v.end());
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 1 3 4 5 6
// Remove_if + erase (one line)
v.erase(std::remove_if(v.begin(), v.end(),
[](int x) { return x % 2 == 1; }),
v.end());
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 4 6
// std::remove does not change size
std::vector<int> demo = {1, 2, 3};
std::cout << "Before remove: " << demo.size() << "\n"; // 3
std::remove(demo.begin(), demo.end(), 1);
std::cout << "After remove: " << demo.size() << "\n"; // 3 (still!)
// Elements are: [2, 3, ?] — size unchanged
}
Removing Duplicates with unique
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4, 5, 5, 6};
// unique only removes CONSECUTIVE duplicates
// Must sort first if you want to remove all duplicates
auto newEnd = std::unique(v.begin(), v.end());
v.erase(newEnd, v.end());
for (int x : v) std::cout << x << " ";
std::cout << "\n"; // 1 2 3 4 5 6
// Using unique with custom predicate
std::vector<int> v2 = {1, 2, 4, 3, 6, 5, 8, 7};
std::sort(v2.begin(), v2.end());
newEnd = std::unique(v2.begin(), v2.end(),
[](int a, int b) { return a % 2 == b % 2; });
v2.erase(newEnd, v2.end());
// Keeps one even and one odd: e.g., 2, 3 (implementation-dependent)
}
std::reverse, std::rotate, std::shuffle
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7};
// reverse
std::reverse(v.begin(), v.end());
// 7 6 5 4 3 2 1
// rotate: shift elements around a middle point
auto mid = v.begin() + 3;
std::rotate(v.begin(), mid, v.end());
// v now: 4 3 2 1 7 6 5
// shuffle (C++11)
std::random_device rd;
std::mt19937 gen(rd());
std::shuffle(v.begin(), v.end(), gen);
for (int x : v) std::cout << x << " ";
std::cout << "\n";
}
Common Mistakes
Mistake 1: Forgetting Output Iterator in Copy
std::vector<int> dest;
std::copy(source.begin(), source.end(), dest.begin()); // UB: dest is empty
Use std::back_inserter(dest) to push elements.
Mistake 2: Not Enough Space for Output
int arr[5];
std::copy_n(source.begin(), 10, arr); // buffer overflow!
Mistake 3: Using remove Without erase
std::remove(v.begin(), v.end(), 3); // size unchanged, v has garbage at end
Always use the erase-remove idiom.
Mistake 4: Using unique on Unsorted Range
unique only removes consecutive duplicates. To remove all duplicates, sort first or use std::adjacent_find in a loop.
Mistake 5: Forgetting That transform Can Be In-Place
Passing the same range as source and destination is valid for transform (in-place modification).
Mistake 6: Using reverse on Non-Bidirectional Iterators
std::forward_list<int> fl = {1, 2, 3};
// std::reverse(fl.begin(), fl.end()); // Error
Use fl.reverse() member function.
Practice Questions
- What is the difference between
std::copyandstd::copy_if? - Why does
std::removenot delete elements? - What does
std::uniquedo? When does it require a sorted range? - Write a one-liner that replaces all odd numbers with 0 in a vector.
- How do you fill a vector with the numbers 1 through 100?
Challenge
Implement a function normalize that takes a vector of strings, trims leading/trailing whitespace, converts to lowercase, removes empty strings, and removes duplicates. Use STL algorithms throughout (no manual loops).
FAQ
Mini Project
Build an image processing pipeline using modifying algorithms:
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
#include <numeric>
struct Image {
std::vector<int> pixels;
int width, height;
Image(int w, int h) : width(w), height(h), pixels(w * h) {}
void fill(int value) {
std::fill(pixels.begin(), pixels.end(), value);
}
void applyBrightness(int delta) {
std::transform(pixels.begin(), pixels.end(), pixels.begin(),
[delta](int p) {
return std::clamp(p + delta, 0, 255);
});
}
void applyThreshold(int threshold) {
std::transform(pixels.begin(), pixels.end(), pixels.begin(),
[threshold](int p) {
return p >= threshold ? 255 : 0;
});
}
void invert() {
std::transform(pixels.begin(), pixels.end(), pixels.begin(),
[](int p) { return 255 - p; });
}
void addNoise(int amount) {
std::default_random_engine gen;
std::uniform_int_distribution dist(-amount, amount);
std::transform(pixels.begin(), pixels.end(), pixels.begin(),
[&](int p) {
return std::clamp(p + dist(gen), 0, 255);
});
}
void print() const {
for (int y = 0; y < height; ++y) {
for (int x = 0; x < width; ++x) {
char c = pixels[y * width + x] > 128 ? '#' : ' ';
std::cout << c;
}
std::cout << "\n";
}
}
};
int main() {
Image img(20, 10);
img.fill(100);
img.addNoise(50);
img.invert();
img.applyThreshold(128);
img.print();
}
What's Next
Modifying algorithms transform data. The next lesson covers numeric algorithms: accumulate, inner_product, partial_sum, iota, adjacent_difference, and parallel execution policies.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro