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.