3

I would like to translate the following traditional for loop into a C++11 for-each loop without extra looping constructs:

int a[] = { 5, 6, 7, 8, 9, 10 };
int b[] = { 50, 60, 70, 80, 90, 100 };

// Swap a and b array elements 
for (int i = 0; i < sizeof(a)/sizeof(a[0]); i++)
{
   a[i] ^= b[i]; b[i] ^= a[i]; a[i] ^= b[i];
}

Does there exist any way by which it is possible to provide more than one variable in the C++11 for-each loop like:

for (int i, int j : ...)
max66
  • 65,235
  • 10
  • 71
  • 111
Seshadri R
  • 1,192
  • 14
  • 24
  • 1
    If you want to do this in a C++11 way, consider using `std::vector>` - of course, if the elements need to be independent then this is no good. – Olipro Jul 21 '16 at 12:48
  • In C++17, structured bindings will allow `for( auto[i, j] : expression() )`. Of course, the value returned by `expression()` must be either an aggregate type with two values, or a tuple-like object with `std::get` accessors. In C++11, you're going to have to use pairs. – KABoissonneault Jul 21 '16 at 13:02
  • Well, if `a` and `b` were `std::vector` to "swap a and b array elements" you only need `std::swap(a,b)` without any loop at all... AFAIK ranged loop can have only one variable. – Bob__ Jul 21 '16 at 13:04
  • @http://stackoverflow.com/users/4944425/bob Thanks for gently reminding me on not reinventing the wheel. I have provided the swap part only to exemplify my requirement of for-each feature. – Seshadri R Jul 22 '16 at 04:11

3 Answers3

5

There is no built-in way to do this. If you can use Boost, boost::combine will work for iterating two (or more) ranges simultaneously (Does boost offer make_zip_range?, How can I iterate over two vectors simultaneously using BOOST_FOREACH?):

for (boost::tuple<int&, int&> ij : boost::combine(a, b)) {
    int& i = boost::get<0>(ij);
    int& j = boost::get<1>(ij);
    // ...
}

Unfortunately accessing the elements within the tuple elements of the zipped range is highly verbose. C++17 will make this much more readable using structured binding:

for (auto [&i, &j] : boost::combine(a, b)) {
    // ...
}

Since you don't need to break out of the loop or return from the enclosing function, you could use boost::range::for_each with the body of your loop as a lambda:

boost::range::for_each(a, b, [](int& i, int& j)
{
    // ...
});
Community
  • 1
  • 1
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • This answers my query. As I am not eligible to upvote this, I am leaving my acknowledgement as a comment here. – Seshadri R Jul 22 '16 at 04:14
  • @SeshadriR what UmNyobe is trying to say is that you can **accept** my answer to let everyone know that it solves your problem: http://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – ecatmur Jul 22 '16 at 12:55
  • @ecatmur Done. Apparently, I could also upvote your answer. – Seshadri R Jul 22 '16 at 13:22
  • `auto [&i, &j]` isn't valid syntax in c++17; also, structured bindings + range-based for don't seem to work with `boost::combine` - https://stackoverflow.com/q/55585723/8414561 – Dev Null Apr 09 '19 at 05:35
  • There is a way to get them working with boost::combine: https://stackoverflow.com/questions/55585723/boostcombine-range-based-for-and-structured-bindings – Audrius Meškauskas Jan 16 '20 at 15:10
2

zip or combine ranges are common in many range libraries.

Writing one strong enough for a for(:) loop isn't hard however.

First we write a basic range type:

template<class It>
struct range_t {
  It b,e;
  It begin() const{ return b; }
  It end() const{ return e; }
  range_t without_front( std::size_t count = 1 ) const {
    return {std::next(begin()), end()};
  }
  bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {b,e}; }
template<class C>
auto range( C& c ) {
  using std::begin; using std::end;
  return range( begin(c), end(c) );
};

Then we write an iterator that works with ranges (easier than with iterators):

template<class R1, class R2>
struct double_foreach_iterator {
  R1 r1;
  R2 r2;
  void operator++() { r1 = r1.without_front(); r2 = r2.without_front(); }
  bool is_end() const { return r1.empty() || r2.empty(); }
  auto operator*()const {
    return std::tie( *r1.begin(), *r2.begin() );
  }
  using self=double_foreach_iterator;
  auto cur() const {
    return std::make_tuple( r1.begin(), r2.begin() );
  }
  friend bool operator==( self const& lhs, self const& rhs ) {
    if (lhs.is_end() || rhs.is_end())
      return lhs.is_end() == rhs.is_end();
    return lhs.cur() == rhs.cur(); 
  }
  friend bool operator!=( self const& lhs, self const& rhs ) {
    return !(lhs==rhs);
  }
};

now we double iterate:

template<class A, class B>
auto zip_iterate(
  A& a, B& b
) {
  auto r1 = range(a);
  auto r2 = range(b);
  auto r1end = range(r1.end(), r1.end());
  auto r2end = range(r2.end(), r2.end());

  using it = double_foreach_iterator<decltype(r1), decltype(r2)>;
  return range( it{r1, r2}, it{r1end, r2end} );
}

which gives us:

for (auto tup : zip_iterate(a, b)) {
  int& i = std::get<0>(tup);
  int& j = std::get<1>(tup);
  // ...
}

or in C++17:

for (auto&& [i, j] : zip_iterate(a, b)) {
  // ...
}

My zip iterate does not assume the two containers are of the same length, and will iterate to the length of the shorter one.

live example.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • nice solution; but I suppose is `return r1.empty() || r2.empty();` instead of `return r1->empty() || r2->empty();` (in `double_foreach_iterator::is_end()`) – max66 Jul 22 '16 at 12:55
  • @max they where, in the first pass, pointers not value members. :) Also note that due to the design of my double iterator, repeatedly recursively double iterating can take up exponentially more space than required (as each of my iterators is **4** of the lower tier iterator, not 2). This is unlikely to be a practical problem. The pointer-based solution mitigates this somewhat, but the efficiency didn't seem worth the risk of dangling pointers. – Yakk - Adam Nevraumont Jul 22 '16 at 13:33
  • `auto [&i, &j]` is not valid c++17 – Dev Null Apr 09 '19 at 05:36
  • @devn fixed. It was 2016! – Yakk - Adam Nevraumont Apr 09 '19 at 12:13
  • @Yakk-AdamNevraumont yes, but we need to have correct answers on SO even in 2019! – Dev Null Apr 10 '19 at 05:58
0

Just for fun.

The following isn't intended to be a serious answer to the question but just an exercise to try to understand the potentiality of C++11 (so, please, be patient).

The following is an example of a class (a draft of a class) that receive a couple of container (with size() method), with the same size (exception otherwise), and of a custom iterator that return a std::pair of std::reference_wrapper to n-position elements.

With a simple use example that show that it's possible to change the value in the starting containers.

Doesn't work with old C-style arrays but works with std::array. We're talking about C++11 so I suppose we could impose the use of std::array.

#include <array>
#include <vector>
#include <iostream>
#include <functional>


template <typename T1, typename T2>
class pairWrapper
 {
   public:

      using V1  = typename std::remove_reference<decltype((T1().at(0)))>::type;
      using V2  = typename std::remove_reference<decltype((T2().at(0)))>::type;
      using RW1 = std::reference_wrapper<V1>;
      using RW2 = std::reference_wrapper<V2>;

      class it
       {
         public:

            it (pairWrapper & pw0, std::size_t p0): pos{p0}, pw{pw0}
             { }

            it & operator++ ()
             { ++pos; return *this; }

            bool operator!= (const it & it0)
             { return pos != it0.pos; }

            std::pair<RW1, RW2> & operator* ()
             {
               static std::pair<RW1, RW2>
                  p{std::ref(pw.t1[0]), std::ref(pw.t2[0])};

               p.first  = std::ref(pw.t1[pos]);
               p.second = std::ref(pw.t2[pos]);

               return p;
             }

         private:

            std::size_t pos;

            pairWrapper &  pw;
       };

      it begin()
       { return it(*this, 0U); }

      it end()
       { return it(*this, len); }

      pairWrapper (T1 & t10, T2 & t20) : len{t10.size()}, t1{t10}, t2{t20}
       { if ( t20.size() != len ) throw std::logic_error("no same len"); }

   private:

      const std::size_t  len;

      T1 &  t1;
      T2 &  t2;
 };


template <typename T1, typename T2>
pairWrapper<T1, T2> makePairWrapper (T1 & t1, T2 & t2)
 { return pairWrapper<T1, T2>(t1, t2); }


int main()
 {
   std::vector<int>    v1 { 1, 2, 3, 4 };
   std::array<long, 4> v2 { { 11L, 22L, 33L, 44L } };

   for ( auto & p : makePairWrapper(v1, v2) )
    {
      std::cout << '{' << p.first << ", " << p.second << '}' << std::endl;

      p.first  +=  3;
      p.second += 55;
    }

   for ( const auto & i : v1 )
      std::cout << '[' << i << ']' << std::endl;

   for ( const auto & l : v2 )
      std::cout << '[' << l << ']' << std::endl;

   return 0;
 }

p.s.: sorry for my bad English

max66
  • 65,235
  • 10
  • 71
  • 111