7

I have a C++ container and I want to run a loop the same number of times as there are elements in that container. But I do not care about the values in the container during the loop. For example:

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

The only problem is, dummy is an unused variable and I have instructed the compiler to prohibit those.

Two inelegant solutions I have come up with are to say (void)dummy; in the loop body to silence the compiler, or to use an old-style for loop from 0 to distance(begin(input), end(input)).

I tried omitting the variable name but that failed to compile (no big surprise).

I'm using GCC 4.7.2.

John Zwinck
  • 239,568
  • 38
  • 324
  • 436
  • 3
    why do you want to loop container and ignore it's content? what are you trying to archive? – billz Apr 04 '14 at 08:08
  • 1
    @bilz, I suspect the number of items is what is pertinent (and the number of times it must be performed) rather than the actual content and that OP wants to use auto-iterators rather than the usual `for` loop. – Moo-Juice Apr 04 '14 at 08:11
  • 2
    If that's the case, what he wants is just to get `input.size()` – billz Apr 04 '14 at 08:12
  • @billz agreed... seems like a lot of effort just to use a C++11 feature rather than something the language already supports (a for loop). – Moo-Juice Apr 04 '14 at 08:13
  • 1
    Range for is used to iterate every elements in a container. If you don't access elements, why do you use range for? For example, no one uses a while and execute exactly fixed one time in block instead of using if statement. – cbel Apr 04 '14 at 08:24
  • 6
    culinary.SE - So, I have this bowl of soup. How do I use a knife to eat it? – ScarletAmaranth Apr 04 '14 at 08:33
  • 1
    Why not use `std::for_each`? Something like `std::for_each(input.begin(), input.end(), [](type) { std::cout << '.'; });` – Rapptz Apr 04 '14 at 08:35
  • 1
    someone went ahaed and gave -1 to every answer. might that person be so kind and explain why all answers are wrong? – example Apr 04 '14 at 08:38
  • `boost::for_each(input, [](auto&& /*ignored*/) { ... });` – gnzlbg Apr 04 '14 at 11:04
  • @billz: For example, I may want to take one container (vector or array or something else) and construct a "parallel" vector by doing emplace_back() once for each element in the original container. I can't use resize() if the value_type is non-copyable. – John Zwinck Apr 04 '14 at 14:15
  • @Rapptz: thanks, that's a reasonable suggestion. I find it a bit ugly, but give what you have to work with, it's not bad. – John Zwinck Apr 04 '14 at 14:17
  • 1
    @cbel Allow me to introduce you to just such a use case, [`do ... while(0)`](http://stackoverflow.com/questions/154136/do-while-and-if-else-statements-in-c-c-macros). – Casey Apr 04 '14 at 17:57
  • @JohnZwinck You give the example "...construct a parallel vector by doing emplace_back()...", so in your real-world use of this piece of code you'd actually do something with the "dummy" variable, and no warning would appear. Or am I missing something? – Piotr99 Apr 07 '14 at 07:31
  • @Piotr99: no, the element inserted in the parallel vector would not make use of `dummy`. In my real use case I'd insert a class instance which does not depend on dummy; the class is non-copyable so I can't use `vec.resize(input.size(), T())` unfortunately. – John Zwinck Apr 07 '14 at 10:16

7 Answers7

5

No need for explicit loops.

use std::begin;
use std::end;
std::cout << std::string(std::distance(begin(input), end(input)), '.');

Or in non-generic context:

std::cout << std::string(input.size(), '.');

If you want to do something more complicated in the loop, just go with (void)dummy;, really. It's clear, well-known and works.

Also look at <algorithm>; what you are implementing may be better implemented in terms of those functions. C++ Seasoning is a nice talk about this.

  • 1
    Because `input.size()` is too simple? – rubenvb Apr 04 '14 at 08:41
  • OK, fair enough. I should stop meddling with underspecified questions. – rubenvb Apr 04 '14 at 08:42
  • @rubenvb: I did write in the OP that I could use `std::distance`. Here it has been inferred that I actually wanted to know "how can I write a series of N dots" which is not what I asked. I apologize for not writing a more sophisticated bit of logic inside the for loop. – John Zwinck Apr 04 '14 at 14:20
3

As all decent suggestions are only comments (some belonging to deleted answers by now), I'll gather them here. Bottom line is: you shouldn't be looping at all.

Instead, use the size of the container and do whatever you want to do that many times. Proposed variants of the code you gave:

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

And the ones that still loop (not recommended:

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

Hint: pick the first one. It's not only the shortest, it expresses what you are doing in the clearest way possible.

rubenvb
  • 74,642
  • 33
  • 187
  • 332
  • 1
    I doubt he actually wants to print a '.' for every entry..... Something more involved that he omitted for this minimal working example is much more likely. – example Apr 04 '14 at 08:41
  • 2
    @example That is not mentioned anywhere in the question. –  Apr 04 '14 at 08:42
  • He clearly states that his intent is to have a loop and gives the source as an example. – example Apr 04 '14 at 08:43
  • 1
    @example "to have a loop" is not his intent. The only thing that is clear is that the OP wants to execute something N times. – rubenvb Apr 04 '14 at 08:47
  • @example: You are correct: printing a dot N times is not the "meat" of my question. Let's just say that "something" needs to be done N times, such as calling a function with a fixed set of arguments. For that reason, `std::for_each` with the lambda is the best solution that wasn't in my original question, I think. – John Zwinck Apr 04 '14 at 14:24
2

If the operation that you actually want to perform is the trivial one that you wrote in your example, a much better way to achieve what you want is:

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

Otherwise, if you just want to loop over a range and perform an operation for each element, ignoring the element value, the for_each <algorithm> is exactly what you want.

  • 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 */; 
    });
    

Note that within the lambda you cannot break or continue, but you can return early to achieve the same behavior.

Still, you should take a look at the other STL algorithms and choose the one that matches your intent best (it will generally also be the fastest).

gnzlbg
  • 7,135
  • 5
  • 53
  • 106
  • 1
    This taught me about `boost::distance()`. Thanks for that. – John Zwinck Apr 04 '14 at 14:25
  • Could I just point out that `STL != stdlib`? I was confused when learning C++, because people used the term `STL` when referring to `stdlib`, which is what the `std` namespace is. A quick look at when `for_each` was added to `stdlib`, tells me that there never was any `for_each` in `STL`. https://stackoverflow.com/a/5205571/7395227; https://en.cppreference.com/w/cpp/algorithm/for_each – Andreas is moving to Codidact Dec 31 '18 at 01:55
  • The STL is the Standard Template Library and is a subset of the C++ standard library. The `` header is part of the STL, and the `for_each` algorithm has been a part of it since before C++ was standardized (e.g. see the SGI STL docs: http://www.martinbroadhurst.com/stl/for_each.html ). – gnzlbg Dec 31 '18 at 09:43
1
for (const auto& dummy __attribute__((unused)) : input) {
    cout << '.';
}

as you are using gcc anyways. As a c++11 attribute this is written as (thanks to Jonathan Wakely)

for (const auto& dummy [[gnu::unused]] : input) {
    cout << '.';
}
example
  • 3,349
  • 1
  • 20
  • 29
  • -1: This is a compiler-specific extension, and terrible terrible style. – rubenvb Apr 04 '14 at 08:44
  • @rubenvb although some description for the code from the author would be appreciated, OP explicitly mentioned C++11, so I can only agree it's a bad style. – ElmoVanKielmo Apr 04 '14 at 08:57
  • 1
    @rubenvb I honestly thought that it was an attribute in the c++11 sense as well - but turns out it is not. It is still a correct answer. It solves the states problem - very much unlike `cout << string(input.size(), '.')`. – example Apr 04 '14 at 09:24
  • The C++11 equivalent would be `for (const auto& dummy [[gnu::unused]] : input)`, which uses a vendor-specific attribute, using the `gnu` attribute-namespace – Jonathan Wakely Apr 04 '14 at 09:29
  • example: You're correct, this solves the stated problem, and taught me a new place I can put the "unused" attribute. So thank you. And thanks to @JonathanWakely for showing us the C++11 way. – John Zwinck Apr 04 '14 at 14:33
0

Another option is to place a call to a dummy inline function.

Define a dummy function.

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

Use the dummy function.

for (const auto& dummy : input) {
    noop(dummy);
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • 2
    Not sure how this is an improvement over `(void)dummy` with the exception that you're perhaps showing intent - but at this point we're using lots of fluffs and work arounds instead of, say, a for loop! – Moo-Juice Apr 04 '14 at 08:22
  • 1
    +1 [According to Herb Sutter](http://herbsutter.com/2009/10/18/mailbag-shutting-up-compiler-warnings/#comment-1509), `(void)dummy` is only partly effective (it doesn't work with [EDG based](http://www.edg.com/) compilers). – manlio Jun 15 '15 at 14:58
0

Well, you can silence the warning with a pragma: #pragma GCC diagnostic push #pragma GCC diagnostic error "-Wunused-variable" /code here/ #pragma GCC diagnostic pop

But I urge you not to do that. Given the tradeoffs, the "(void)dummy" option is better. I also urge you not to do a dummy inline function as advized by R Sahu, as this just obfuscates your code and unnecessary complexity.

I think in this case billz and moo-juice gave good advice. You are probably better off doing this with an old style for loop.

I can think of one situation where you might want to use the options discussed here. If the container "input" is of a type where getting the size requires traversing the range, and if the code in question is going to be in a performance critical area, you may find it worthwhile to optimize out iterating over the loop twice. In that case use: (void) dummy; // silence uneccessary warning.

However this is almost certainly not the case in the example code you have posted, where io operations will dominate, making this a premature optmization.

Remember that you code will be read many more times than it will be written, so focus on what makes the code most readable. Using new C++ features is only a benefit if it bring an increase in the readability, maintainability, or flexibility of your code -- or if it brings a meaningful performance improvement.

In the case of the example you've posted, your guiding principle should be "what the easiest for a colleague to read?".

Spacemoose
  • 3,856
  • 1
  • 27
  • 48
  • I cannot believe your answer got 2 downvotes in just a few hours. I actually think it's reasonable and covers some of the important bases. +1. – John Zwinck Apr 04 '14 at 14:11
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>());
}

then:

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

should work with standard containers and arrays at nearly optimal efficiency. It does count all of the elements in a forward_list before starting the repeat code.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524