用 C++ 来 enumerate()

不少编程语言都提供了在迭代容器的同时记录步数的方法,例如 Python 的 enumerate()

for i, elem in enumerate(v):
    print(i, elem)

以及 Rust 里 std::iter::Iterator 特性下的 enumerate()

for (i, elem) in v.iter().enumerate() {
    println!("{}, {}", i, elem);
}

这里记录了如何在 C++17 或更新的标准里尽量简洁地实现类似功能的办法。

第一种方法是使用一个可变的 lambda :

std::for_each(v.begin(), v.end(),
              [i = 0](auto elem) mutable {
                  std::cout << i << ", " << elem << std::endl;
                  ++i;
              });

这个方法使用于所有能够保证 lambda 有序执行的算法,但是我并不喜欢末尾很可能被混入其他逻辑的 ++i

第二种方法是在 for 循环中使用结构化绑定:

for (auto [i, elem_it] = std::tuple{0, v.begin()}; elem_it != v.end();
     ++i, ++elem_it) {
    std::cout << i << ", " << *elem_it << std::endl;
}

为了不让编译器默认创建只允许同种内容的 std::initializer_list ,我们必须加上 std::tuple

第三种最朴实无华的办法是在循环的每一步计算指针距离:

for (auto elem_it = v.begin(); elem_it != v.end(); ++elem_it) {
    auto i = std::distance(v.begin(), elem_it);
    std::cout << i << ", " << *elem_it << std::endl;
}

由于这种方法需要我们在两个地方指定初始指针,我更喜欢之前提到的基于计数器的方法。

在 C++20 中,我们可以在基于范围的 for 循环中加入初始化语句:

for (auto i = 0; auto elem : v) {
    std::cout << i << ", " << elem << std::endl;
    i++;
}

新加入的 <ranges> 库则提供了一种更加吸引人的实现方法:

for (auto [i, elem] : v | std::views::transform(
         [i = 0](auto elem) mutable { return std::tuple{i++, elem}; })) {
    std::cout << i << ", " << elem << std::endl;
}

我最喜欢基于结构化绑定和 <ranges> 库的方法。当然如果要是有 std::views::enumerate 来一劳永逸地解决这个问题就最好不过了。

评论区




暂时没有评论。