Skip to content

C++ Range Based For Loops with integer counter/index variable

License

Notifications You must be signed in to change notification settings

LostInCompilation/RangeBasedForLoop-WithCounter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

32 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Range-Based For Loop with counter variable

Platform Version CPP

⚠️ README will be updated for the new version soon.
Small single header utility to add an integer counter/index variable to C++ Range-Based For Loops. Full cross platform support.

Contents

See also: License (zlib)

Simple example

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.

Console output:
0: Element 1
1: Element 2
2: Element 3

Description

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).

Motivation

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.

Supported container types

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

Usage

Installation

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;

Basic usage

Simply use the count(...) or rcount(...) (reverse count) function for everything. See the documentation of the count/rcount function down below for further info.

  • STL Container

    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;
  • Associative containers like std::map

    ⚠️ Supported. README will be updated soon.

  • Iterators

    You can also use std::iterator to specify a range. This will print only the first two elements of vec.

    for(auto [value, index] : count(vec.begin(), vec.begin() + 2))
        std::cout << index << ": " << value << std::endl;
  • C-Style Arrays

    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;
  • r-Values

    The count and rcount 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 and rcount must become an owning view for r-Values. This means that what ever r-Value you pass to count or rcount will be copied inside of it.

    for(auto [value, index] : count(std::vector<std::string>{"X", "Y", "Z"}))
        std::cout << index << ": " << value << std::endl;
  • Initializer list

    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;

Using an offset for index

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.

Console output:
42: Element 1
43: Element 2
44: Element 3

Reverse

There are two different types of reversing, which can also be combined:

  • Reverse index

    index
  • Reverse elements

    elements

To combine both modes, use rcount with the last parameter (reverse index) set to true.

Count function overview

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.

Parameters

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 type and variable types

return

Overloads

  • 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 normal count function, which then behaves like rcount: std::rbegin(...) and std::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

  1. You can use a workaround and copy, for example, a std::queue into a temporary std::vector which then can be used with the count(...) function. But this introduces run time overhead.