Get index in C++11 foreach loop

后端 未结 5 742
太阳男子
太阳男子 2020-12-28 12:55

Is there a convenient way to get the index of the current container entry in a C++11 foreach loop, like enumerate in python:

for idx, obj in enu         


        
相关标签:
5条回答
  • 2020-12-28 13:40

    A good implementation of the feature you are requested can be found here:

    https://github.com/ignatz/pythonic

    The idea behind is, that you build a wrapper struct with a custom iterator that does the counting. Below is a very minimal exemplary implementation to illustrate the idea:

    #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;
        }
    }
    
    0 讨论(0)
  • 2020-12-28 13:50

    What about a simple solution like:

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

    You could make a bit more "difficult" to add code after the counter by adding a scope:

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

    As @graham.reeds pointed, normal for loop is also a solution, that could be as fast:

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

    And finally, a alternative way using algorithm:

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

    Note: the order between range loop and normal loop is guaranteed by the standard 6.5.4. Meaning the counter is able to be coherent with the position in the container.

    0 讨论(0)
  • 2020-12-28 13:54

    If you have access to Boost it's range adaptors can be used like this:

    using namespace boost::adaptors;
    
    for (auto const& elem : container | indexed(0))
    {
        std::cout << elem.index() << " - " << elem.value() << '\n';
    }
    

    Source (where there are also other examples)

    0 讨论(0)
  • 2020-12-28 13:56

    If you need the index then a traditional for works perfectly well.

    for (int idx=0; idx<num; ++idx)
    {
    // do stuff
    }
    
    0 讨论(0)
  • 2020-12-28 14:01

    C++17 and structured bindings makes this look OK - certainly better than some ugly mutable lambda with a local [i = 0](Element&) mutable or whatever I've done before admitting that probably not everything should be shoehorned into for_each() et al. - and than other solutions that require a counter with scope outside the for loop.

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

    You could make this generic, if you use this pattern often enough:

    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++ Standard proposal P2164 proposes to add views::enumerate, which would provide a view of a range giving both reference-to-element and index-of-element to a user iterating it.

    We propose a view enumerate whose value type is a struct with 2 members index and value representing respectively the position and value of the elements in the adapted range.

    [ . . .]

    This feature exists in some form in Python, Rust, Go (backed into the language), and in many C++ libraries: ranges-v3, folly, boost::ranges (indexed).

    The existence of this feature or lack thereof is the subject of recurring stackoverflow questions.

    Hey, look! We're famous.

    0 讨论(0)
提交回复
热议问题