当前位置: 动力学知识库 > 问答 > 编程问答 >

Python-like loop enumeration in C++

问题描述:

Possible Duplicate:

Find position of element in C++11 range-based for loop?

I have a vector and I would like to iterate it and, at the same time, have access to the indexes for each individual element (I need to pass both the element and its index to a function). I have considered the following two solutions:

std::vector<int> v = { 10, 20, 30 };

// Solution 1

for (std::vector<int>::size_type idx = 0; idx < v.size(); ++idx)

foo(v[idx], idx);

// Solution 2

for (auto it = v.begin(); it != v.end(); ++it)

foo(*it, it - v.begin());

I was wondering whether there might be a more compact solution. Something similar to Python's enumerate. This is the closest that I got using a C++11 range-loop, but having to define the index outside of the loop in a private scope definitely seems to be like a worse solution than either 1 or 2:

{

int idx = 0;

for (auto& elem : v)

foo(elem, idx++);

}

Is there any way (perhaps using Boost) to simplify the latest example in such a way that the index gets self-contained into the loop?

网友答案:

As @Kos says, this is such a simple thing that I don't really see the need to simplify it further and would personally just stick to the traditional for loop with indices, except that I'd ditch std::vector<T>::size_type and simply use std::size_t:

for(std::size_t i = 0; i < v.size(); ++i)
    foo(v[i], i);

I'm not too keen on solution 2. It requires (kinda hidden) random access iterators which wouldn't allow you to easily swap the container, which is one of the strong points of iterators. If you want to use iterators and make it generic (and possibly incur a performance hit when the iterators are not random access), I'd recommend using std::distance:

for(auto it(v.begin()); it != v.end(); ++it)
    foo(*it, std::distance(it, v.begin());
网友答案:

Here is some kind of funny solution using lazy evaluation. First, construct the generator object enumerate_object:

template<typename Iterable>
class enumerate_object
{
    private:
        Iterable _iter;
        std::size_t _size;
        decltype(std::begin(_iter)) _begin;
        const decltype(std::end(_iter)) _end;

    public:
        enumerate_object(Iterable iter):
            _iter(iter),
            _size(0),
            _begin(std::begin(iter)),
            _end(std::end(iter))
        {}

        const enumerate_object& begin() const { return *this; }
        const enumerate_object& end()   const { return *this; }

        bool operator!=(const enumerate_object&) const
        {
            return _begin != _end;
        }

        void operator++()
        {
            ++_begin;
            ++_size;
        }

        auto operator*() const
            -> std::pair<std::size_t, decltype(*_begin)>
        {
            return { _size, *_begin };
        }
};

Then, create a wrapper function enumerate that will deduce the template arguments and return the generator:

template<typename Iterable>
auto enumerate(Iterable&& iter)
    -> enumerate_object<Iterable>
{
    return { std::forward<Iterable>(iter) };
}

You can now use your function that way:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& a: enumerate(vec)) {
        size_t index = std::get<0>(a);
        double& value = std::get<1>(a);

        value += index;
    }
}

The implementation above is a mere toy: it should work with both const and non-const lvalue-references as well as rvalue-references, but has a real cost for the latter though, considering that it copies the whole iterable object several times. This problem could surely be solved with additional tweaks.

Since C++17, decomposition declarations even allow you to have the cool Python-like syntax to name the index and the value directly in the for initializer:

int main()
{
    std::vector<double> vec = { 1., 2., 3., 4., 5. };
    for (auto&& [index, value] a: enumerate(vec)) {
        value += index;
    }
}

I don't have a C++17-compliant compiler at hand to check it, but I hope that the auto&& in the decomposition is able to infer index as std::size_t and value as double&.

网友答案:

One way is to wrap the loop in a function of your own.

#include <iostream>
#include <vector>
#include <string>

template<typename T, typename F>
void mapWithIndex(std::vector<T> vec, F fun) {
   for(int i = 0; i < vec.size(); i++) 
       fun(vec[i], i); 
}

int main() {
   std::vector<std::string> vec = {"hello", "cup", "of", "tea"};
   mapWithIndex(vec, [](std::string s, int i){
      std::cout << i << " " << s << '\n';
   } );
}
分享给朋友:
您可能感兴趣的文章:
随机阅读: