Madison A.
Madison A.

Reputation: 13

Way to Use C++ 11's for-range Syntax for Sequential Pairs?

Is there a way to use for-range loop syntax to process two sequential elements in an array?

Example...

func( std::vector< vec2 > &points )
{
std::vector< float > distances;
for( int i = 0; i < (int)points.size() - 1; i++ )
   {
   auto from = points.at( i );
   auto to   = points.at( i + 1 );

   distances.push_back( magnitude( to - from ) );
   }
}

Upvotes: 1

Views: 461

Answers (2)

R Sahu
R Sahu

Reputation: 206627

Is there a way to use for-range loop syntax to process two sequential elements in an array?

Not out of the box.

However, you can roll your own wrapper class and an iterator class to get what you need.

The begin() and end() member functions of the wrapper class must return an iterator which evaluates to a std::pair when it is dereferenced with the * operator.

Here's a demonstrative program:

#include <iostream>
#include <vector>

struct VectorWrapper;

struct MyIterator
{
   MyIterator(VectorWrapper const& wrapper, size_t index) : wrapper_(wrapper), index_(index) {}

   std::pair<float, float> operator*();

   MyIterator& operator++()
   {
      ++index_;
      return *this;
   }

   bool operator==(MyIterator const& rhs) const
   {
      return (this->index_ == rhs.index_);
   }

   bool operator!=(MyIterator const& rhs) const
   {
      return (this->index_ != rhs.index_);
   }

   VectorWrapper const& wrapper_;
   size_t index_;
};

struct VectorWrapper
{
   explicit VectorWrapper(std::vector<float>& distances) : distances_(distances) {}

   MyIterator begin() const
   {
      return MyIterator(*this, 0);
   }

   MyIterator end() const
   {
      return MyIterator(*this, distances_.size()-1);
   }

   std::vector<float>& distances_;
};

std::pair<float, float> MyIterator::operator*()
{
   return std::make_pair(wrapper_.distances_[index_], wrapper_.distances_[index_+1]); 
}

int main()
{
   std::vector<float> dist = {1, 2, 3, 4, 5, 6};
   VectorWrapper wrapper(dist);
   for ( auto item : wrapper )
   {
      std::cout << item.first << ", " << item.second << std::endl;
   }
}

and its output:

1, 2
2, 3
3, 4
4, 5
5, 6

Upvotes: 4

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275585

In with a bit of library help, you can get this to work:

for (auto&&[ from, to ] : adjacent_overlapped_zip( points ) ) {
  distances.push_back( magnitude( to-from) );
}

where adjacent_overlapped_zip returns a range of adpted iterators over pairs or tuples of points.

template<class It>
struct range {
  It b; It e;
  It begin() const{ return b; }
  It end() const{ return e; }
  bool empty() const{ return begin()==end(); }
  range without_front( std::size_t n = 1 ) const {
    return {std::next(begin(), n), end()};
  }
  range without_back( std::size_t n = 1 ) const {
    return {begin(), std::prev(end(), n)};
  }
};
template<class It>
range(It b, It e)->range<It>;

template<class It>
struct adjacent_iterator:It {
  auto operator*()const {
    return std::make_pair( It::operator*(), std::next(*this).It::operator*() );
  }
  using It::It;
  explicit adjacent_iterator(It it):It(it) {}
};
template<class It>
explicit adjacent_iterator( It ) -> adjacent_iterator<It>;

// TODO: support pointers

template<class C>
auto adjacent_overlapped_zip( C& c ) {
  using std::begin; using std::end;
  range r( begin(c), end(c) );
  if (!r.empty()) {
    r = r.without_back();
    range retval( adjacent_iterator(r.begin()), adjacent_iterator(r.end()) );
    return retval;
  } else {
    return {};
  }
}

or something like that. The above code probably contains typos and other errors.

I'd also be tempted by:

for (auto&&[ from, to ] : transform( [](auto it){ return std::make_pair( *it, *std::next(it)); }, range( iterators_of( points ) ).without_back() ) )
  distances.push_back( magnitude( to-from) );
}

with a slightly fancier set of primitives. Ranges-v3 would make this even nicer.


In raw , no you are out of luck.

Upvotes: 1

Related Questions