【问题标题】:C++11 for each loop with more than one variable对于具有多个变量的每个循环的 C++11
【发布时间】:2016-11-25 01:34:14
【问题描述】:

我想将以下传统 for 循环转换为 C++11 for-each 循环,而不需要额外的循环结构:

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];
}

是否有任何方法可以在 C++11 for-each 循环中提供多个变量,例如:

for (int i, int j : ...)

【问题讨论】:

  • 如果你想以 C++11 的方式来做这件事,可以考虑使用std::vector&lt;std::pair&lt;int,int&gt;&gt;——当然,如果元素需要独立,那就不好了。
  • 在 C++17 中,结构化绑定将允许 for( auto[i, j] : expression() )。当然,expression() 返回的值必须是具有两个值的聚合类型,或者是具有 std::get 访问器的类元组对象。在 C++11 中,您将不得不使用对。
  • 好吧,如果 abstd::vector&lt;int&gt; 以“交换 a 和 b 数组元素”,那么您只需要 std::swap(a,b) 而无需任何循环......AFAIK 远程循环可以有只有一个变量。
  • @stackoverflow.com/users/4944425/bob 感谢您温柔地提醒我不要重新发明轮子。我提供交换部分只是为了说明我对 for-each 功能的要求。

标签: c++ c++11 for-loop


【解决方案1】:

没有内置的方法可以做到这一点。如果可以使用 Boost,boost::combine 将同时迭代两个(或更多)范围(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);
    // ...
}

不幸的是,访问压缩范围的元组元素中的元素非常冗长。 C++17 将使用结构化绑定使其更具可读性:

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

由于您不需要跳出循环或从封闭函数返回,您可以使用 boost::range::for_each 将循环体作为 lambda:

boost::range::for_each(a, b, [](int& i, int& j)
{
    // ...
});

【讨论】:

  • 这回答了我的问题。由于我没有资格对此进行投票,因此我在此处留下我的致谢作为评论。
  • @SeshadriR UmNyobe 想说的是,您可以接受我的回答,让大家知道它可以解决您的问题:meta.stackexchange.com/questions/5234/…
  • @ecatmur 完成。显然,我也可以投票赞成你的回答。
  • auto [&amp;i, &amp;j] 在 c++17 中是无效的语法;此外,结构化绑定 + 基于范围的 for 似乎不适用于 boost::combine - stackoverflow.com/q/55585723/8414561
  • 有一种方法可以让他们使用 boost::combine: stackoverflow.com/questions/55585723/…
【解决方案2】:

压缩或组合范围在许多范围库中很常见。

不过,为for(:) 循环编写足够强大的代码并不难。

首先我们编写一个基本的范围类型:

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) );
};

然后我们编写一个使用范围的迭代器(比使用迭代器更容易):

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);
  }
};

现在我们进行两次迭代:

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} );
}

这给了我们:

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

或在 C++17 中:

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

我的 zip 迭代确实假设两个容器的长度相同,并将迭代到较短的容器的长度。

live example.

【讨论】:

  • 不错的解决方案;但我想是return r1.empty() || r2.empty(); 而不是return r1-&gt;empty() || r2-&gt;empty();(在double_foreach_iterator::is_end()
  • @max 它们在第一遍中,指针不是值成员。 :) 另请注意,由于我的双迭代器的设计,重复递归双迭代可能会占用比所需空间更多的空间(因为我的每个迭代器都是下层迭代器的 4,而不是 2 个) .这不太可能是一个实际问题。基于指针的解决方案在一定程度上缓解了这种情况,但效率似乎不值得冒悬空指针的风险。
  • auto [&amp;i, &amp;j] 无效 c++17
  • @devn 已修复。那是 2016 年!
  • @Yakk-AdamNevraumont 是的,但即使在 2019 年,我们也需要对 SO 有正确的答案!
【解决方案3】:

只是为了好玩。

以下内容并不是对这个问题的严肃回答,而只是一个尝试理解 C++11 潜力的练习(所以,请耐心等待)。

下面是一个类(类的草稿)的示例,它接收两个容器(使用size() 方法),具有相同的大小(否则例外),以及一个返回@的自定义迭代器987654322@ of std::reference_wrapper 到 n 位元素。

通过一个简单的使用示例表明可以更改起始容器中的值。

不适用于旧的 C 样式数组,但适用于 std::array。我们谈论的是 C++11,所以我想我们可以强制使用 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.:对不起我的英语不好

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-28
    • 2016-10-02
    • 1970-01-01
    • 2019-01-24
    相关资源
    最近更新 更多