在c++ 11 foreach循环中获取index

Get index in C++11 foreach loop

本文关键字:获取 index 循环 foreach c++      更新时间:2023-10-16

是否有一种方便的方法来获取c++ 11 foreach循环中当前容器条目的索引,如python中的enumerate:

for idx, obj in enumerate(container):
    pass

我可以想象一个迭代器也可以返回索引或类似的东西。

当然可以有一个计数器,但是迭代器通常不能保证它们在容器上迭代的顺序。

您所要求的功能的一个很好的实现可以在这里找到:

https://github.com/ignatz/pythonic

背后的思想是,您构建一个包装器结构体,并使用自定义迭代器进行计数。下面是一个非常简单的示例实现来说明这个想法:

// Distributed under the terms of the GPLv2 or newer
#include <iostream>
#include <vector>
#include <tuple>
// Wrapper class
template <typename T>
class enumerate_impl
{
public:
    // The return value of the operator* of the iterator, this
    // is what you will get inside of the for loop
    struct item
    {
        size_t index;
        typename T::value_type & item;
    };
    typedef item value_type;
    // Custom iterator with minimal interface
    struct iterator
    {
        iterator(typename T::iterator _it, size_t counter=0) :
            it(_it), counter(counter)
        {}
        iterator operator++()
        {
            return iterator(++it, ++counter);
        }
        bool operator!=(iterator other)
        {
            return it != other.it;
        }
        typename T::iterator::value_type item()
        {
            return *it;
        }
        value_type operator*()
        {
            return value_type{counter, *it};
        }
        size_t index()
        {
            return counter;
        }
    private:
        typename T::iterator it;
        size_t counter;
    };
    enumerate_impl(T & t) : container(t) {}
    iterator begin()
    {
        return iterator(container.begin());
    }
    iterator end()
    {
        return iterator(container.end());
    }
private:
    T & container;
};
// A templated free function allows you to create the wrapper class
// conveniently 
template <typename T>
enumerate_impl<T> enumerate(T & t)
{
    return enumerate_impl<T>(t);
}

int main()
{
    std::vector<int> data = {523, 1, 3};
    for (auto x : enumerate(data))
    {
        std::cout << x.index << ": " << x.item << std::endl;
    }
}

简单的解决方案如何:

int counter=0;
for (auto &val: container)
{
    makeStuff(val, counter);
    counter++;
}

您可以通过添加作用域来使在计数器之后添加代码变得更"困难":

int counter=0;
for (auto &val: container)
{{
    makeStuff(val, counter); 
}counter++;}

@graham。芦苇指出,正常的for循环也是一种解决方案,它可以快:

int counter=0;
for (auto it=container.begin(); it!=container.end(); ++it, ++counter)
{
    makeStuff(val, counter);
}

最后,使用算法的另一种方法:

int counter = 0;
std::for_each(container.begin(), container.end(), [&counter](int &val){ 
    makeStuff(val, counter++);
});

注:量程回路与正常回路的顺序由标准6.5.4保证。这意味着计数器能够与容器中的位置一致。

如果您可以访问Boost,则可以这样使用它的范围适配器:

#include <boost/range/adaptor/indexed.hpp>
using namespace boost::adaptors;
 
for (auto const& elem : container | indexed(0))
{
    std::cout << elem.index() << " - " << elem.value() << 'n';
}

源代码(其中还有其他示例)

c++ 17和结构化绑定使这看起来不错-当然比一些丑陋的带有局部[i = 0](Element&) mutable的可变lambda或任何我在承认可能不是所有东西都应该硬塞进for_each() 之前所做的要好-并且比其他解决方案需要for循环之外的计数器。

for (auto [it, end, i] = std::tuple{container.cbegin(), container.cend(), 0};
     it != end; ++it, ++i)
{
      // something that needs both `it` and `i`ndex
}

如果你经常使用这个模式,你可以把它变成通用的:

template <typename Container>
auto
its_and_idx(Container&& container)
{
    using std::begin, std::end;
    return std::tuple{begin(container), end(container), 0};
}
// ...
for (auto [it, end, i] = its_and_idx(foo); it != end; ++it, ++i)
{
    // something
}

c++标准提案P2164建议增加views::enumerate,它将提供一个范围的视图,为迭代它的用户提供对元素的引用和元素的索引。

我们提出一个值类型为struct的视图enumerate,它有两个成员indexvalue,分别表示在适应范围内的元素的位置和值。

[…]

此功能以某种形式存在于Python, Rust, Go(返回到语言中)以及许多c++库中:ranges-v3, folly, boost::ranges (indexed)。

是否存在此特性是反复出现的stackoverflow问题的主题。

嘿,瞧!我们有名。

如果你需要索引,那么传统的for就可以了。

for (int idx=0; idx<num; ++idx)
{
// do stuff
}