Is there an equivalent to the range-based `enumerate` loop from python in modern C++?
Asked Answered
D

8

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

Display answered 27/2, 2015 at 15:56 Comment(2)
What exactly is wrong with the "old school" for loop that you want to use templates or shudders macros, instead?Tribune
Nothing really. I'm just curious to see how it could be done.Display
S
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;
}
Shoot answered 27/2, 2015 at 16:12 Comment(8)
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).Belsky
You'll want to inherit from std::iterator< std::input_iterator_tag, ??? > or do some manual typedefs to be a full on iterator. Not needed for basic for(:) loops I suppose.Cedric
@JosephMansfield enumerateIterator does what you ask, I think. enumerateConstruct just flattens it?Cedric
@JosephMansfield if you use enumerateIterator it has that behavior.Shoot
@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.Shoot
@Shoot 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.Belsky
Naming things is the hardest part of computer scienceShoot
@Shoot 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.Cedric
T
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.

Towny answered 27/2, 2015 at 18:11 Comment(0)
R
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 ;)

Redpencil answered 6/9, 2016 at 9:55 Comment(2)
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: boost.org/doc/libs/1_56_0/libs/range/doc/html/range/reference/…Cowell
That will hopefully be the Standard ranges::views::enumerate() very soon.Account
S
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;
}
Shoot answered 27/2, 2015 at 16:12 Comment(8)
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).Belsky
You'll want to inherit from std::iterator< std::input_iterator_tag, ??? > or do some manual typedefs to be a full on iterator. Not needed for basic for(:) loops I suppose.Cedric
@JosephMansfield enumerateIterator does what you ask, I think. enumerateConstruct just flattens it?Cedric
@JosephMansfield if you use enumerateIterator it has that behavior.Shoot
@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.Shoot
@Shoot 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.Belsky
Naming things is the hardest part of computer scienceShoot
@Shoot 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.Cedric
A
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.

Account answered 23/5, 2020 at 11:31 Comment(0)
P
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.

Puritanism answered 27/2, 2015 at 16:0 Comment(4)
Boost zip rangers and counting iterators can make counting i by hand go away, for values of go away involving some pretty insane code.Cedric
@Yakk: Cool, make this an answer. Might be useful to the OP.Puritanism
@Yakk using boost I can achieve a quick enumerate function. Thanks for the tip.Display
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 mentions that and another, better way to do this in C++17 without having to import any library.Account
K
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 !
Kleeman answered 12/1, 2019 at 3:56 Comment(0)
E
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
}
Entrap answered 13/2, 2019 at 11:49 Comment(0)
C
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;
}
Cowell answered 24/7, 2019 at 0:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.