2019-04-27-enumerate-with-c-plus-plus.en.md (2453B)
1 +++
2 title = "enumerate() with C++"
3 draft = false
4 date = 2019-04-27
5 slug = "enumerate-with-c-plus-plus"
6 +++
7
8 Quite a few programming languages provide ways to iterate through a container while keeping count of the number of steps taken, such as `enumerate()` in Python:
9
10 ```python
11 for i, elem in enumerate(v):
12 print(i, elem)
13 ```
14
15 and `enumerate()` under `std::iter::Iterator` trait in Rust:
16
17 ```rust
18 for (i, elem) in v.iter().enumerate() {
19 println!("{}, {}", i, elem);
20 }
21 ```
22
23 This is just a quick note about how to do similar things in C++17 and later without declaring extra variables out of the for loop's scope.
24
25 The first way is to use a mutable lambda:
26
27 ```c++
28 std::for_each(v.begin(), v.end(),
29 [i = 0](auto elem) mutable {
30 std::cout << i << ", " << elem << std::endl;
31 ++i;
32 });
33 ```
34
35 This could be used with all the algorithms that guarantees in-order application of the lambda, but I don't like the dangling `++i` that could get mixed up with other logic.
36
37 The second way utilizes structured binding in for loops:
38
39 ```c++
40 for (auto [i, elem_it] = std::tuple{0, v.begin()}; elem_it != v.end();
41 ++i, ++elem_it) {
42 std::cout << i << ", " << *elem_it << std::endl;
43 }
44 ```
45
46 We have to throw in `std::tuple` as otherwise compiler would try to create a `std::initializer_list`, which does not allow heterogeneous contents.
47
48 The third least fancy method is to just calculate the distance every time:
49
50 ```c++
51 for (auto elem_it = v.begin(); elem_it != v.end(); ++elem_it) {
52 auto i = std::distance(v.begin(), elem_it);
53 std::cout << i << ", " << *elem_it << std::endl;
54 }
55 ```
56
57 Since we have to copy paste the starting point twice, I like other counter based approaches better.
58
59 In C++20, we have the ability to add an init-statement in ranged-based for loops, so we can write something like
60
61 ```c++
62 for (auto i = 0; auto elem : v) {
63 std::cout << i << ", " << elem << std::endl;
64 i++;
65 }
66 ```
67
68 Meh, not that impressive. The new `<ranges>` library provides a more appealing way to achieve this:
69
70 ```c++
71 for (auto [i, elem] : v | std::views::transform(
72 [i = 0](auto elem) mutable { return std::tuple{i++, elem}; })) {
73 std::cout << i << ", " << elem << std::endl;
74 }
75 ```
76
77 I like the structured binding method and the `<ranges>` based method the most. It would be even better though if we can get a `std::views::enumerate` to solve this problem once and for all.