How to Use the Same Variables for Both Forward and Reverse Iterators in C++
The Challenge of Differing Iterator Types in C++
In C++, std::vector<T>::iterator and std::vector<T>::reverse_iterator are completely different, incompatible types. Because C++ is a statically-typed language, you cannot declare a single pair of iterator variables (like it_b and it_e) and assign either forward or reverse iterators to them at runtime using a simple if-else block.
However, there are several elegant, modern, and highly efficient ways to achieve this behavior without duplicating your loop logic. Let's explore the best solutions, ranging from classic C++ templates to modern C++20/C++23 ranges.
Solution 1: The Idiomatic STL Way (Generic Lambda / Helper Function)
Instead of trying to store the iterators in the outer scope, the standard and most performant C++ approach is to pass the iterators to a template function or a generic lambda. This allows the compiler to generate the correct loop code for both iterator types with zero runtime overhead.
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {10, 20, 30, 40, 50};
int n = -1; // Condition variable
// Define a generic lambda to handle the loop logic
auto process_loop = [](auto begin, auto end) {
for (auto it = begin; it != end; ++it) {
std::cout << *it << " ";
}
std::cout << "\n";
};
// Conditionally pass the appropriate iterators
if (n >= 0) {
process_loop(nums.begin(), nums.end());
} else {
process_loop(nums.rbegin(), nums.rend());
}
return 0;
}Solution 2: Modern C++20 Ranges and Views
If you are using C++20, you can avoid dealing with raw iterators altogether. Instead of conditionally selecting iterators, you can conditionally apply a reverse view to your container. This keeps your code clean, readable, and highly declarative.
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> nums = {10, 20, 30, 40, 50};
int n = -1;
auto execute_loop = [](auto&& range) {
for (int val : range) {
std::cout << val << " ";
}
std::cout << "\n";
};
if (n >= 0) {
execute_loop(nums);
} else {
execute_loop(nums | std::views::reverse);
}
}Solution 3: Type Erasure with C++23 std::ranges::any_view
If you absolutely need a single variable to hold either the forward range or the reversed range, C++23 introduces std::ranges::any_view. This class template provides type erasure for views, allowing you to reassign different view types to the same variable.
#include <iostream>
#include <vector>
#include <ranges>
int main() {
std::vector<int> nums = {10, 20, 30, 40, 50};
int n = -1;
// Type-erased view that yields 'int&'
std::ranges::any_view<int> view;
if (n >= 0) {
view = nums;
} else {
view = nums | std::views::reverse;
}
for (int val : view) {
std::cout << val << " ";
}
}Summary: Which Approach Should You Choose?
- Use a Generic Lambda (Solution 1) if you want a highly performant, zero-overhead solution that works seamlessly across C++14 and later.
- Use C++20 Views (Solution 2) for modern, readable code that avoids raw iterator manipulation.
- Use C++23
any_view(Solution 3) if you strictly require a single variable to represent the conditional range.