5

Is there an equivalent to the range-based enumerate loop from python in C++? I would imagine something like this.

enumerateLoop (auto counter, auto el, container) {
    charges.at(counter) = el[0];
    aa.at(counter) = el[1];
}

Can this be done with templates or macros?

I'm aware that I can just use an old school for-loop and iterate until I reach container.size(). But I'm interested how this would be solved using templates or macros.

EDIT

I played a bit with boost iterators after the hint in the comments. I got another working solution using C++14.

template <typename... T>
auto zip(const T &... containers) -> boost::iterator_range<boost::zip_iterator<
decltype(boost::make_tuple(std::begin(containers)...))>> {
  auto zip_begin =
    boost::make_zip_iterator(boost::make_tuple(std::begin(containers)...));
  auto zip_end =
    boost::make_zip_iterator(boost::make_tuple(std::end(containers)...));
  return boost::make_iterator_range(zip_begin, zip_end);
}

template <typename T>
auto enumerate(const T &container) {
return zip(boost::counting_range(0, static_cast<int>(container.size())),
container);
} 

https://gist.github.com/kain88-de/fef962dc1c15437457a8

Max Linke
  • 1,705
  • 2
  • 18
  • 24

8 Answers8

4

Enumeration of multiple variables has been an idiom since C. The only complication is that you can't declare both variables in the initializer of the for loop.

int index;
for (auto p = container.begin(), index = 0; p != container.end(); ++p, ++index)

I don't think it gets any simpler (or more powerful) than that.

QuestionC
  • 10,006
  • 4
  • 26
  • 44
4

There is a pre C++11 solution in boost to this: boost.range.indexed. Unfortunately it doesn't work with C++11 range based for-loops, only old style verbose loops. However with C++17 it should be become (almost) as easy as in python using structured bindings

Then it should be possible implement something that works like this:

for (auto& [n,x] : enumerate(vec)) x = n;

So, a bit of waiting still ;)

André Bergner
  • 1,429
  • 10
  • 10
  • boost range indexed didn't work with range for until 1.56 but does now. Strangely I can't seem to find the documentation of this change except in the example which is obnoxious since it was a breaking change: https://www.boost.org/doc/libs/1_56_0/libs/range/doc/html/range/reference/adaptors/reference/indexed.html – Catskul Jul 24 '19 at 00:53
  • 2
    That will hopefully be the Standard `ranges::views::enumerate()` [very soon](https://stackoverflow.com/a/61971356/2757035). – underscore_d May 28 '20 at 08:52
3

I wrote something for this a while back.

Essentially, you need to wrap an iterator and give it pair semantics.

AFAIK, there's nothing like this built into the language. And I don't think boost has it either. You pretty much have to roll your own.

// Wraps a forward-iterator to produce {value, index} pairs, similar to
// python's enumerate()
template <typename Iterator>
struct EnumerateIterator {
private:
  Iterator current;
  Iterator last;
  size_t index;
  bool atEnd;

public:
  typedef decltype(*std::declval<Iterator>()) IteratorValue;
  typedef pair<IteratorValue const&, size_t> value_type;

  EnumerateIterator()
    : index(0), atEnd(true) {}

  EnumerateIterator(Iterator begin, Iterator end)
    : current(begin), last(end), index(0) {
    atEnd = current == last;
  }

  EnumerateIterator begin() const {
    return *this;
  }

  EnumerateIterator end() const {
    return EnumerateIterator();
  }

  EnumerateIterator operator++() {
    if (!atEnd) {
      ++current;
      ++index;

      atEnd = current == last;
    }

    return *this;
  }

  value_type operator*() const {
    return {*current, index};
  }

  bool operator==(EnumerateIterator const& rhs) const {
    return
      (atEnd && rhs.atEnd) ||
      (!atEnd && !rhs.atEnd && current == rhs.current && last == rhs.last);
  }

  bool operator!=(EnumerateIterator const& rhs) const {
    return !(*this == rhs);
  }

  explicit operator bool() const {
    return !atEnd;
  }
};

template<typename Iterable>
EnumerateIterator<decltype(std::declval<Iterable>().begin())> enumerateIterator(Iterable& list) {
  return EnumerateIterator<decltype(std::declval<Iterable>().begin())>(list.begin(), list.end());
}

template<typename ResultContainer, typename Iterable>
ResultContainer enumerateConstruct(Iterable&& list) {
  ResultContainer res;
  for (auto el : enumerateIterator(list))
    res.push_back(move(el));

  return res;
}
OmnipotentEntity
  • 16,531
  • 6
  • 62
  • 96
  • 1
    Nice. I think it'd be better if `enumerate` returned a range that generated the pairs on the fly (rather than building up an entire copy of the vector with pairs in it). – Joseph Mansfield Feb 27 '15 at 16:17
  • You'll want to inherit from `std::iterator< std::input_iterator_tag, ??? >` or do some manual `typedef`s to be a full on iterator. Not needed for basic `for(:)` loops I suppose. – Yakk - Adam Nevraumont Feb 27 '15 at 16:18
  • @JosephMansfield `enumerateIterator` does what you ask, I think. `enumerateConstruct` just flattens it? – Yakk - Adam Nevraumont Feb 27 '15 at 16:19
  • @JosephMansfield if you use `enumerateIterator` it has that behavior. – OmnipotentEntity Feb 27 '15 at 16:19
  • @Yakk, yeah, it would be nice to revisit it and add all of the helper functions to make it fully random access. Nothing's really stopping me other than laziness and a complete lack of need. – OmnipotentEntity Feb 27 '15 at 16:21
  • @OmnipotentEntity Ah, missed it. Thanks. Odd to see a class called `...Iterator` that implements `begin` and `end`. Would expect these to be split out into a class that represents a range. – Joseph Mansfield Feb 27 '15 at 16:23
  • Naming things is the hardest part of computer science – OmnipotentEntity Feb 27 '15 at 16:25
  • @OmnipotentEntity random access isn't possibly while lazy, because random access iterators are forward, and forward iterators require `operator*` to return a real reference. And you want lazy iterators. – Yakk - Adam Nevraumont Feb 27 '15 at 20:04
3

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.

underscore_d
  • 6,309
  • 3
  • 38
  • 64
2

You can also more elegantly use the auto ranges available since C++11:

int i = 0;
for (auto& el : container){
    charges.at(counter) = el[0];
    aa.at(counter) = el[1];
    ++i;
}

You still have to count the i up by hand, though.

steffen
  • 8,572
  • 11
  • 52
  • 90
  • Boost zip rangers and counting iterators can make counting `i` by hand go away, for values of go away involving some pretty insane code. – Yakk - Adam Nevraumont Feb 27 '15 at 16:11
  • 1
    @Yakk: Cool, make this an answer. Might be useful to the OP. – steffen Feb 27 '15 at 16:13
  • @Yakk using boost I can achieve a quick enumerate function. Thanks for the tip. – Max Linke Mar 19 '15 at 21:05
  • Surely you mean `i` instead of `counter`, or vice-versa? Anyway, I always feel such constructs would be cleaner by using a mutabe lambda to hold the counter, rather than letting it leak into the outer scope (or having to declare a new one). [This answer](https://stackoverflow.com/a/59599186/2757035) mentions that and another, better way to do this in C++17 without having to import any library. – underscore_d May 28 '20 at 08:54
0

Here's a macro-based solution that probably beats most others on simplicity, compile time, and code generation quality:

#include <iostream>

#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)

int main() {
    fori(i, auto const & x : {"hello", "world", "!"}) {
        std::cout << i << " " << x << std::endl;
    }
}

Result:

$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate 
0 hello
1 world
2 !
Forrest Voight
  • 2,249
  • 16
  • 21
0

Tobias Widlund wrote a nice MIT licensed Python style header only enumerate (C++17 though):

GitHub

Blog Post

Really nice to use:

std::vector<int> my_vector {1,3,3,7};

for(auto [i, my_element] : en::enumerate(my_vector))
{
    // do stuff
}
M. Ahnen
  • 21
  • 3
0

Boost::Range supports this as of 1.56.

#include <boost/range/adaptor/indexed.hpp>
#include <boost/assign.hpp>
#include <iterator>
#include <iostream>
#include <vector>


int main(int argc, const char* argv[])
{
    using namespace boost::assign;
    using namespace boost::adaptors;

    std::vector<int> input;
    input += 10,20,30,40,50,60,70,80,90;

//  for (const auto& element : index(input, 0)) // function version
    for (const auto& element : input | indexed(0))      
    {
        std::cout << "Element = " << element.value()
                  << " Index = " << element.index()
                  << std::endl;
    }

    return 0;
}
Catskul
  • 17,916
  • 15
  • 84
  • 113