Implementation of Python-like enumerate() Function in C++

2024-07-16C++

Introduction

In the previous article, you created a function that has similar functionality to Python’s zip() function. This allows you to handle multiple containers at once in a single range-based for loop.

However, with the range-based for loop, you don’t have access to the current element’s index. To achieve that, you would need to write it in a way that does not use the range-based for loop, as shown below:

std::vector<int> a = [1, 2, 3];

for(size_t i = 0; i < a.size(); i++){
    std::cout << a[i] << std::endl;
}

It is true that it can sometimes feel inconvenient to not take advantage of iterators when they are available.

When working with languages like Python, the presence of the enumerate() function, which allows for handling indices along with elements as shown below, can be quite handy.

a = [1, 2, 3]

for i, elem in enumerate(a):
  print(f"{i}: {elem}")

Using C++17’s structured bindings, it seems you can achieve a similar functionality. Therefore, in this article, I attempted to create an Enumerator class that uses tuples to handle indices and elements together, along with an Enumerate() function that returns instances of the Enumerator class.

Implementation and Usage

Implementation

Please refer to the source code uploaded on GitHub.

To apply range-based for loops similarly to the Zipper class, begin() and end() are implemented as member functions of the container, along with the implementation of iterators. When using the dereference operator * of the iterator, it returns a tuple of index and container element. By utilizing GetReference<> created in this article, it also supports arrays.

In the code uploaded to GitHub, a specialized Enumerator class is implemented to handle the Zipper class created in the previous article. In this class, an object of the Zipper class is stored as a member variable (not as a reference) to enable receiving right-value references in Enumerate(Zipper &&).

Usage

Here is an example of its usage. Let’s assume that zipper.h and enumerator.h are in the same directory. (zipper.h is included in enumerator.h)

#include <iostream>
#include <list>
#include <map>
#include <string>
#include <vector>

#include "enumerator.h"

int main()
{
    std::vector<int> a = {1, 2, 3};
    std::list<std::string> b = {"a", "b", "c"};
    std::map<int, std::string> c = {{0, "abc"}, {1, "def"}, {2, "ghi"}};

    for (auto [i, s, t, u] : Enumerate(Zip(a, b, c))) {
      std::cout << "[" << i << "] " << s << " " << t << " " << u.first << " " << u.second << std::endl;
    }

    return 0;
}

Conclusion

I tried creating a Python-like Enumerate() function using tuples. It allows you to combine index and element in one for loop. By combining it with the Zip() function, you can also handle multiple containers’ elements and indexes together.

Note that in C++23, the std::views::enumerate() function is standardized in <ranges> as well. It’s advisable to use that if you’re in an environment that supports C++23.

std::vector<int> a = {1, 2, 3};
std::list<std::string> b = {"aaa", "bbb", "ccc"};

for(auto & [i, p, q]: std::views::enumerate(std::views::zip(a, b))) {
    std::println("{}: {}, {}", i, p, q);
}

C++

Posted by izadori