Small single header utility to add an integer counter/index variable to C++ Range-Based For Loops. Full cross platform support.
See also: License (zlib)
std::vector<std::string> vec = {"Element 1", "Element 2", "Element 3"};
for(auto [value, index] : count(vec))
std::cout << index << ": " << value << std::endl; // Index will be incremented automatically
Where value
is of type std::string&
, and index
is of type const std::iterator_traits<std::vector<std::string>::iterator>::difference_type&
, which can be implicitly cast to an int
.
0: Element 1
1: Element 2
2: Element 3
With this small header utility you can easily add an index variable to Range Based For Loops, without any verbose code.
You can use any STL-Container, std::initializer_list
or custom types derived from them. l-Values and r-Values are both supported.
Additionally you can specify a counter offset, to start counting at a different value than zero (see Using an offset).
Reverse counting is also supported in two different variants. Reversing the enumeration of the elements itself (start with last element in container) or just reverse the index variable (start index at number of elements in container and count down to zero). Both modes can be combined (see Reverse).
While declaring a separate counter variable right above the Range Based For Loop would work, it adds quite some verbosity to the code. Also the counter variable's scope would be valid outside of the loop too, which can lead to some nasty name clashes. With C++20 we got initialization in Range Based For Loops, but this also adds verbosity to the code by declaring a separate variable and incrementing it:
for (int index = 0; auto& value : vec)
i++; // Increment still needed
The above example can also introduce some nasty lifetime issues with more complex types, since Range Based For Loops destroy any temporaries before actually running.
I wanted to make a simple and universal solution, with almost no verbosity and the flexibility to count up or down and start counting based on an offset.
Type | Container | Supported |
---|---|---|
C-Style Array | int myArr[42]; |
✅ Yes |
Sequence | std::array std::vector std::deque std::forward_list std::list |
✅ Yes |
Associative | std::set std::map std::multiset std::multimap std::unordered_set std::unordered_map std::unordered_multiset std::unordered_multimap |
✅ Yes |
Adaptors | std::stack std::queue std::priority_queue std::flat_set std::flat_map std::flat_multiset std::flat_multimap |
❌ No These types aren't iterable and don't support Range Based For Loops1 |
Special | std::initializer_list std::iterator |
✅ Yes |
Include the RangeForLoopWithCounter.h
header and use the RBFLCounter
namespace and you're good to go. Requires C++20.
#include "RangeForLoopWithCounter.h"
using namespace RBFLCounter;
Simply use the count(...)
or rcount(...)
(reverse count) function for everything. See the documentation of the count/rcount function down below for further info.
-
Usage with STL-Containers is straightforward:
std::vector<std::string> vec = {"Element 1", "Element 2", "Element 3"}; for(auto [value, index] : count(vec)) std::cout << index << ": " << value << std::endl;
-
⚠️ Supported. README will be updated soon. -
You can also use
std::iterator
to specify a range. This will print only the first two elements ofvec
.for(auto [value, index] : count(vec.begin(), vec.begin() + 2)) std::cout << index << ": " << value << std::endl;
-
C-Style arrays can be used in the same way:
int arr[] = {42, 43, 44, 45, 46, 47}; for(auto [value, index] : count(arr)) std::cout << index << ": " << value << std::endl;
-
The
count
andrcount
functions fully support r-Values and move semantics. However due to the design of Range Based For Loops in C++ (they destroy every temporary before actually running),count
andrcount
must become an owning view for r-Values. This means that what ever r-Value you pass tocount
orrcount
will be copied inside of it.for(auto [value, index] : count(std::vector<std::string>{"X", "Y", "Z"})) std::cout << index << ": " << value << std::endl;
-
An
std::initializer_list
can also be used (l-Values and r-Values):for(auto [value, index] : count({"L1", "L2", "L3"})) std::cout << index << ": " << value << std::endl;
To use an offset for the counter variable, simply pass an offset to the
count(Container, Offset=0)
or rcount(Container, Offset=0)
function:
std::vector<std::string> vec = {"Element 1", "Element 2", "Element 3"};
for(auto [value, index] : count(vec, 42)) // Start counting at 42
std::cout << index << ": " << value << std::endl;
The default offset value is zero.
42: Element 1
43: Element 2
44: Element 3
There are two different types of reversing, which can also be combined:
- index
- elements
To combine both modes, use rcount
with the last parameter (reverse index) set to true.
The count
and rcount
functions provide different overloads and parameters for usage with different types and to adjust the behaviour of the counting.
The count
and rcount
functions are constexpr
.
The general usage is count(Container, Offset=0, ReverseIndex=false)
and rcount(Container, Offset=0, ReverseIndex=false)
:
Container
is any type of container or array.Offset
is the offset from where to start counting. Default is zero.ReverseIndex
is a boolean to enable counting in reverse for the index (start at number of elements in container, counting down to zero). Default is false (no reverse index counting).
return
-
C-Style arrays
template<typename T, std::size_t size> count(T (&arr)[size], const std::ptrdiff_t& offset = 0)
-
Iterators
template<typename IteratorType> count(const IteratorType& first, const IteratorType& last, const typename std::iterator_traits<IteratorType>::difference_type& offset = 0)
Note: There is no
rcount
function for iterators, since you can just pass a reverse iterator to the normalcount
function, which then behaves likercount
:std::rbegin(...)
andstd::rend(...)
. -
l-Value container and l-Value
std::initializer_list
template<typename ContainerType> count(ContainerType& container, const typename std::iterator_traits<typename ContainerType::iterator>::difference_type& offset = 0)
-
r-Value container
template<typename ContainerType> count(ContainerType&& container, const typename std::iterator_traits<typename ContainerType::iterator>::difference_type& offset = 0)
-
r-Value
std::initializer_list
template<typename T> count(std::initializer_list<T>&& init_list, const std::ptrdiff_t& offset = 0)
Footnotes
-
You can use a workaround and copy, for example, a
std::queue
into a temporarystd::vector
which then can be used with thecount(...)
function. But this introduces run time overhead. ↩