【问题标题】:C++11 range-based for loops without loop variable没有循环变量的 C++11 基于范围的 for 循环
【发布时间】:2013-07-17 22:52:54
【问题描述】:

在 C++ 中,我需要迭代一定次数,但不需要迭代变量。例如:

for( int x=0; x<10; ++x ) {
    /* code goes here, i do not reference "x" in this code */
}

我意识到我可以通过用 lambda 或命名函数替换“代码在此处”来做到这一点,但这个问题专门针对 for 循环。

我希望 C++11 基于范围的 for 循环会有所帮助:

for( auto x : boost::irange(0,10) ) {
    /* code goes here, i do not reference "x" in this code */
}

但上面给出了一个“未引用的局部变量”,因为我从未明确引用 x。

我想知道是否有更优雅的方式来编写上述 for 循环,以便代码不会生成“未引用的局部变量”警告。

【问题讨论】:

  • 为了澄清这一点,我的解释是 OP 正在尝试使用基于范围的 for 循环来做到这一点。但是,for (auto x : boost::irange(0, 10)) f(); 之类的内容会发出警告,因为 x 未使用。
  • 但是变量引用(在循环条件下)。编译器绝对不应该对该代码发出警告。
  • @rodrigo:我不这么认为,循环条件使用了隐藏的迭代器。 x 被写入但从未读取,导致编译器发出警告。
  • @MooingDuck x&lt;10; 读取 x?
  • @Yakk:for (auto x : boost::irange(0, 10)) 不包含表达式x&lt;10。它包含一个更像auto x = *__secret_iterator1; 的语句。

标签: c++ c++11


【解决方案1】:

编辑现在声明的循环变量减少了 100%。

template <typename F>
void repeat(unsigned n, F f) {
    while (n--) f();
}

将其用作:

repeat(10, f);

repeat(10, [] { f(); });

int g(int);
repeat(10, std::bind(g, 42));

观看http://ideone.com/4k83TJ

【讨论】:

  • @chris 这取决于,你想编译代码吗?还是做点什么?通过对 lambda 实例进行操作,这两者都将得到改善。 :)
  • @Yakk,我感觉我只是错过了编辑或阅读不正确。
  • @chris 我在函数模板中有F() 而不是f()。现已修复
  • @sehe 你也可以将() 放到[]{ f(); } 中,我会在[] 中填充一个&amp;,这样它的行为就像一个子作用域。
  • @Yakk 我将把它留给实际用户。我只想展示的是,您可以传递 lambda 实例、绑定表达式,或者实际上是任何可调用对象。
【解决方案2】:

可能有办法做到这一点,但我非常非常怀疑它会更优雅。您在第一个循环中所拥有的已经是正确的方法,限制了循环变量的范围/生命周期。

我会简单地忽略未使用的变量警告(毕竟,这只是编译器提示可能有问题)或使用编译器工具(如果可用)简单地关闭警告那时。

这可能通过某种#pragma 来实现,具体取决于您的环境,或者某些实现允许您执行以下操作:

for (int x = 0; x < 10; ++x) {
    (void)x;

    // Other code goes here, that does not reference "x".
}

我已经看到 void 技巧用于函数体中未使用的参数。

【讨论】:

  • 到目前为止,只有真正值得投票的答案。可能值得一提的是,C++11 规范说循环变量(“for-range-declaration”)是强制性的。
  • 我会说为此发出的编译器警告完全被破坏了。
  • 我认为,实际上建议人们忽略编译器警告是很危险的。这导致程序编译时带有大量(被忽略的)警告,这些警告隐藏了偶尔需要的、重要的和有用的警告。正是出于这个原因,我永远不会接受我的项目没有一个警告就无法编译的补丁......
  • @cmaster,我不建议他们盲目地忽略所有警告,只是在代码中的 that 点发出 that 警告。一旦您理解了警告并意识到您比编译器更了解,就可以这样做。由于很可能是我对“任何”一词的使用导致您认为我的意思是“全部”而不是预期的零或一,因此我已将其删除:-)
  • 很难忽略一些警告。这意味着你必须记住它,只要你维护代码,如果一个新的开发者来了,他必须明白警告是无害的......我肯定会使用某种UNUSED(var) 宏来抑制警告。
【解决方案3】:

假设10 是一个编译时间常数...

#include <cstddef>
#include <utility>
template<std::size_t N>
struct do_N_times_type {
  template<typename Lambda>
  void operator()( Lambda&& closure ) const {
    closure();
    do_N_times_type<N-1>()(std::forward<Lambda>(closure));
  }
};
template<>
struct do_N_times_type<1> {
  template<typename Lambda>
  void operator()( Lambda&& closure ) const {
    std::forward<Lambda>(closure)();
  }
};
template<>
struct do_N_times_type<0> {
  template<typename Lambda>
  void operator()( Lambda&& closure ) const {
  }
};

template<std::size_t N, typename Lambda>
void do_N_times( Lambda&& closure ) {
  do_N_times_type<N>()( std::forward<Lambda>(closure) );
};
#include <iostream>
void f() {
  std::cout << "did it!\n";
}
int main() {
  do_N_times<10>([&]{
    f();
  });
}

或者只是

int main() {
  do_N_times<10>(f);
}

其他可笑的方法:

编写一个范围迭代器(我叫我的index),它产生一系列迭代器-on-integral 类型(我默认为std::size_t)。然后输入:

for( auto _:index_range(10) )

它使用了一个变量 (_) 但看起来非常混乱。

另一种疯狂的方法是创建一个类似 python 的生成器。编写一个生成器包装器,它接受一个可迭代的范围并生成一个函数,该函数在该范围的value_type 上返回std::optional

我们可以这样做:

auto _ = make_generator( index_range(10) );
while(_()) {
}

它也创建了一个临时变量,而且更加迟钝。

我们可以编写一个在生成器上运行的循环函数:

template<typename Generator, typename Lambda>
void While( Generator&& g, Lambda&& l ) {
  while(true) {
    auto opt = g();
    if (!opt) return;
    l(*opt);
  }
}

然后我们称之为:

While( make_generator( index_range(10) ), [&](auto&&){
  f();
});

但这既在函数中创建了一些临时变量,而且比上一个更荒谬,并且依赖于甚至尚未最终确定的 C++1y 的特性。

我尝试创建一种无变量的方式来重复某件事 10 次。

但实际上,我只是做循环。

您几乎可以通过输入x=x; 来阻止警告

或者写一个函数

template<typename Unused>
void unused( Unused&& ) {}

然后调用unused(x); -- 使用了变量x,它的名字被放到了里面,所以编译器可能不会在里面警告你。

所以试试这个:

template<typename Unused>
void unused( Unused&& ) {}
for(int x{};x<10;++x) {
  unused(x);
  f();
}

应该抑制警告,并且实际上很容易理解。

【讨论】:

  • 为什么 1 是特殊情况?似乎没有必要
  • 为什么所有的模板魔法?我的答案是 3 行,并且还将编译为编译时常量 n...(最近在 gcc/clang 上验证)的完全展开循环
  • @sehe 谁信任优化器?但说真的,OP 要求消除循环变量:所以我认真对待 OP。在实践中,您应该只取消警告并执行 OP 所写的操作。另外,我在传入的仿函数上小心地调用operator()&amp;&amp;,以防仿函数优化这种情况。 (注意:没有人优化这种情况)
  • 看代码就知道是你写的。
  • 这些看起来很可怕!这是一个荒谬的代码量,所有这些都是为了避免一开始就不应该出现的编译器警告。我的意思是,这在代码高尔夫中令人印象深刻,但我绝不会考虑在生产代码中使用类似的东西。大多数人会花费大量时间试图弄清楚到底发生了什么,这不值得因为没有迭代变量而获得任何抽象收益。
【解决方案4】:

实际上有一种方法可以完成这项工作。您需要做的就是返回一个std::array,其长度由您提供的常量指定:

template <int N>
using range = std::array<int, N>;

int main()
{
    for (auto x : range<5>())
    {
        std::cout << "Awesome\n";
    }
}

输出:

真棒
真棒
真棒
真棒
太棒了

Here is a demo.

注意:这是假设范围说明符是一个编译时常量,所以如果你必须使用一个变量,请确保它被有效地标记为constexpr

【讨论】:

  • 10 / 10 的风格,但请注意,即使将 auto x 更改为 const auto&amp; x 也不会阻止 Clang 3.0 生成 subq %rsp, $hugenumber ... callq memset 以及漂亮的循环。因此,在编译器支持赶上之前,将这种方法与range&lt;100000&gt; 一起使用可能是个坏主意。
【解决方案5】:

在我看来,您滥用了基于范围的循环。当逻辑为:“为集合中的每个元素做某事”时,应使用基于范围的循环。整个想法是摆脱索引变量,因为它并不重要。如果您有一个集合,您应该使用必要的 API 对其进行检测,以启用基于范围的迭代。如果您没有集合,那么您就没有必要使用基于范围的循环(实际上,这就是编译器以不那么有用的方式暗示的意思)。在这种情况下,正常的 for/while 循环是自然的选择。

【讨论】:

    【解决方案6】:

    没有任何方法可以创建一个基于工作的范围来简单地迭代多个数字。

    C++11 基于范围的循环需要一个范围表达式,它可能是:

    • 数组或
    • 一个类具有
      • 成员函数begin()end()
      • 可用的免费函数 begin()end()(通过 ADL)

    除此之外:基于 for 的范围会产生一些开销:

    for ( for_range_declaration : expression ) statement
    

    扩展到

    range_init = (expression)
    {
      auto && __range = range_init;
      for ( auto __begin = begin_expr,
      __end = end_expr;
      __begin != __end;
      ++__begin ) {
        for_range_declaration = *__begin;
        statement;
      }
    }
    

    其中 begin_expr 和 end_expr 是通过数组检查或begin() / end() 对获得的。

    我认为这不会比简单的 for 循环更“干净”。尤其是在性能方面。 没有调用,只是一个简单的循环。

    我能想出让它更优雅的唯一方法(优雅显然取决于我的看法)是在此处使用大小或无符号类型:

    for(size_t x(0U); x<10U; ++x) f();
    

    【讨论】:

    • for(unsigned x{},x&lt;10u;++x) 可以说更优雅
    • @MooingDuck for(int x{};x&lt;10;++x) 保存字符!
    • @Yakk:我们应该发布一个问题,看看我们是否可以让语法更加优雅。
    • @MooingDuck 我不同意。 1.它甚至不会编译,因为你有一个错字。 2. 当前的 MSVC 版本不支持这种大括号初始化。牺牲 2 个字符的可移植性并不优雅。
    • 便携性和优雅性是正交 IMO。有很多优雅的代码是不可移植的。此外,代码理论上是可移植的,它根本不适用于在该领域未确认的编译器。在现实世界中,我完全明白你的意思,并且永远不会做我输入的内容。
    【解决方案7】:

    已经在https://stackoverflow.com/a/21800058/1147505 中得到了最好的回答:如何定义一个 UNUSED 宏以在整个代码库中使用,从而抑制此警告。以便携的方式。

    【讨论】:

    • 我想那时我宁愿只写出没有基于范围的语法的 for 循环(即 for(uint x=0; x
    【解决方案8】:

    您可以将 STL 与 lambda 表达式一起使用。

    #include <algorithm>
    #include <iostream>
    
    int main() {
        int a[] = {1,2,3,4,5,6};
    
        std::for_each(std::begin(a),
                std::end(a),
                [](int){std::cout << "Don't care" << std::endl;});
    }
    

    这种方法也适用于任意容器,例如向量或列表。让vector&lt;int&gt; a,然后你会打电话给a.begin()a.end()。请注意,您也可以使用函数指针代替 lambda 表达式。

    以上内容保留了您使用 foreach 的概念,而不是抱怨未使用的参数。

    【讨论】:

    • 漂亮、干净、惯用的
    【解决方案9】:

    这适用于 GCC 和 clang 以及任何支持 gnu 属性的编译器:

    for( [[gnu::unused]] auto x : boost::irange(0,10) ) {
    

    并且应该在任何 c++11 编译器中编译,但如果编译器无法识别 gnu 属性,则可能不会抑制警告。

    【讨论】:

      【解决方案10】:

      参加比赛:

      #include <iostream>
      #include <boost/range/irange.hpp>
      using namespace std;
      using namespace boost;
      
      namespace {
          template<typename T> inline void 
          unused(const T&) {}
      }
      
      int main() {
          for (auto&& i : irange(0,10)) {
              unused(i);
              cout << "Something" << endl;
          }
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2021-04-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-01
        • 2013-01-04
        相关资源
        最近更新 更多