C++11的基于范围的for循环:如何忽略值?

7
我有一个C++容器,我想要运行一个循环,循环次数与容器中元素数量相同。但是在循环期间,我不关心容器中的值。例如:
for (const auto& dummy : input) {
    cout << '.';
}

唯一的问题是,dummy是一个未使用的变量,我已经指示编译器禁止这些变量。
我想到了两个不太优雅的解决方案,一个是在循环体中使用 (void)dummy; 来消除编译器警告,另一个则是使用传统的 for 循环,从 0 到 distance(begin(input), end(input))
我试着省略变量名,但是编译失败了(并不让人意外)。
我正在使用 GCC 4.7.2。

3
为什么要循环遍历容器并忽略其内容?你想要达到什么目的? - billz
1
@bilz,我怀疑重要的是项目数量(以及必须执行的次数),而不是实际内容,并且OP想要使用自动迭代器而不是通常的for循环。 - Moo-Juice
2
如果是这种情况,他想要的只是获取 input.size() - billz
1
Range for循环用于迭代容器内的每个元素。如果您不访问元素,为什么要使用range for?例如,没有人会使用while循环在块中执行精确一次而不是使用if语句。 - cbel
6
烹饪技术 网站 - 所以,我有一碗汤。如何使用刀子来吃? - ScarletAmaranth
显示剩余9条评论
7个回答

5
无需显式循环。
use std::begin;
use std::end;
std::cout << std::string(std::distance(begin(input), end(input)), '.');

或者在非泛型上下文中:
std::cout << std::string(input.size(), '.');

如果你想在循环中做一些更复杂的操作,只需使用 (void)dummy;。这是清晰众所周知有效的

同时,看看<algorithm>;你正在实现的内容可能最好是用这些函数来实现。 C++调味是一个关于此的好演讲。


1
因为 input.size() 太简单了? - rubenvb
好的,说得对。我应该停止干涉未明确说明的问题。 - rubenvb
@rubenvb:我在原帖中确实写了我可以使用std::distance。但是这里被推断出我实际上想知道“如何编写一系列N个点”,这不是我所问的。对于在for循环内部编写更复杂的逻辑,我表示歉意。 - John Zwinck

3

由于所有合理的建议都只是评论(现在有些属于已删除的答案),因此我将把它们汇总在这里。底线是:你根本不应该循环。

相反,使用容器的大小,然后按照需要执行相应次数的操作即可。以下是您提供的代码的建议变体:

cout << string(input.size(), '.'); // @Matt
std::fill_n(std::ostream_iterator<char>(std::cout), input.size(), '.'); // @Hilborn

同时仍然循环的代码(不推荐):

std::for_each(input.begin(), input.end(), [](type) { std::cout << '.'; }); // @Rapptz
std::transform(input.begin(), input.end(), std::ostream_iterator<char>(std::cout),
[] (auto&&) { return '.'; }); // @hilborn

提示:选择第一个。它不仅最短,而且以最清晰的方式表达了您正在做的事情。

1
我怀疑他实际上并不想为每个条目打印一个'.'..... 他省略了更复杂的内容,这对于这个最小工作示例来说更有可能。 - example
2
这个问题中没有提到那一点。 - user1804599
他明确表示他的意图是要有一个循环,并且给出了源代码作为示例。 - example
1
“拥有一个循环”不是他的意图。唯一清楚的是,OP想要执行某个操作N次。 - rubenvb
你说得对:打印N个点并不是我问题的“重点”。我们只需要做“某件事” N 次,比如使用一组固定的参数调用函数。因此,使用带有lambda表达式的std::for_each是我原始问题中没有的最佳解决方案,我认为。 - John Zwinck

2

如果您想执行的操作与您在示例中写的微不足道的操作相同,那么实现您想要的结果的更好方法是:

std::cout << std::string{boost::distance(input), '.'};

否则,如果您只想循环遍历一个范围并对每个元素执行操作,而忽略元素的值,则 for_each <algorithm> 正是您需要的。
  • Using boost:

    #include <boost/range/algorithm.hpp>
    boost::for_each(input, [](auto&& /*ignored*/) { /* do stuff */; });
    
  • Using the STL:

    #include <algorithm>
    std::for_each(std::begin(input), std::end(input), [](auto&& /*ignored*/) { 
      /* do stuff */; 
    });
    
请注意,在lambda表达式中你不能使用breakcontinue,但是你可以提前使用return来实现相同的行为。
尽管如此,你应该查看其他STL算法,并选择最符合你意图的算法(通常也是最快的)。

1
这让我了解了boost::distance()。谢谢。 - John Zwinck
我能指出一下 STL != stdlib 吗?在学习 C++ 时,我很困惑,因为人们在提到 stdlib 时使用了术语 STL,而 std 命名空间就是 stdlib。快速查看 for_each 何时添加到 stdlib 中,告诉我 STL 中从未有过任何 for_each。https://dev59.com/UG435IYBdhLWcg3wyzaa#5205571; https://en.cppreference.com/w/cpp/algorithm/for_each - Andreas detests censorship
STL是标准模板库,是C++标准库的一个子集。<algorithm>头文件是STL的一部分,而for_each算法自C++标准化之前就已经成为其中的一部分(例如,请参阅SGI STL文档:http://www.martinbroadhurst.com/stl/for_each.html)。 - gnzlbg

1
for (const auto& dummy __attribute__((unused)) : input) {
    cout << '.';
}

既然您已经在使用gcc,那么作为C++11属性,它应该写成(感谢Jonathan Wakely)

for (const auto& dummy [[gnu::unused]] : input) {
    cout << '.';
}

-1:这是一个特定于编译器的扩展,非常糟糕的风格。 - rubenvb
@rubenvb 虽然对于代码的描述可能会很有帮助,但是OP明确提到了C++11,所以我只能同意这种写法并不好。 - ElmoVanKielmo
1
@rubenvb 我一开始也以为它是C++11中的属性,但事实证明它并不是。不过这仍然是一个正确的答案。它解决了状态问题,与 cout << string(input.size(), '.') 非常不同。 - example
C++11的等效写法是 for (const auto& dummy [[gnu::unused]] : input),它使用了供应商特定的属性,使用了gnu属性命名空间。 - Jonathan Wakely
你说得对,这解决了所述的问题,并教会了我一个可以放置“未使用”属性的新位置。非常感谢。也感谢@JonathanWakely向我们展示了C++11的方法。 - John Zwinck

0

好的,你可以使用#pragma来消除警告:

#pragma GCC diagnostic push #pragma GCC diagnostic error "-Wunused-variable" /在此处插入代码/ #pragma GCC diagnostic pop

但我建议你不要这样做。考虑到权衡,"(void)dummy"选项更好。我也建议你不要像R Sahu建议的那样做一个虚拟的内联函数,因为这只会使你的代码变得混乱且复杂。

我认为在这种情况下,billz和moo-juice给出了很好的建议。你最好使用旧式的for循环。

我能想到一种情况,你可能想使用这里讨论的选项。如果容器"input"是一种需要遍历范围才能获取大小的类型,并且所讨论的代码将在性能关键区域中运行,你可能会发现优化掉两次循环的迭代是值得的。在这种情况下,请使用:

(void) dummy; // 消除不必要的警告。

然而,在你发布的示例代码中,几乎肯定不是这种情况,因为io操作将占主导地位,这是一种过早的优化。

请记住,您的代码将被阅读的次数比编写的次数多得多,因此请专注于使代码更易读。只有当使用新的C++功能可以增加代码的可读性、可维护性或灵活性——或者它带来了有意义的性能提升时,才会带来好处。

对于您发布的示例,您的指导原则应该是“对同事来说最容易阅读的是什么?”


我简直不敢相信你的答案在短短几个小时内就获得了两个反对票。实际上,我认为它是合理的,涵盖了一些重要的方面。点赞。 - John Zwinck

0

另一种选择是调用一个虚拟的内联函数。

定义一个虚拟函数。

template <typename T> void noop(T const& t){} 

使用虚拟函数。

for (const auto& dummy : input) {
    noop(dummy);
}

2
不确定这比(void)dummy有什么改进,除了你可能展示了意图 - 但此时我们正在使用大量的垃圾代码和变通方法,而不是例如for循环! - Moo-Juice
1
根据Herb Sutter的说法,(void)dummy只有部分有效(它不能与基于EDG的编译器一起使用)。 - manlio

0
template<typename F>
void repeat(std::size_t n, F&&f){
  if(!n) return;
  while(--n) f();
  std::forward<F>(f)(); // enable rvalue operator()
}
template<class C,class=void>struct has_size:std::false_type{};
template<class C>struct has_size<C,decltype(
  std::declval<C>().size(),void()
)>:std::true_type{};
template<typename C>
std::size_t size_helper(C&&c,std::false_type){
  using std::begin; using std::end;
  return std::distance(begin(c),end(c));
}
template<typename C>
std::size_t size_helper(C&&c, std::true-type){
  return c.size();
}
template<typename C>
std::size_t size(C&&c){
  return size_helper(c, has_size<C>());
}

然后:

repeat(size( source ),[&]{
  std::cout<<".";
});

应该以接近最优的效率与标准容器和数组一起工作。在开始重复代码之前,它会计算 forward_list 中的所有元素。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接