5

Is there a common idiom for doing something twice, as in the following situation?

    for ( int i = 0; i < num_pairs; i++ ) {
        cards.push_back( Card(i) );
        cards.push_back( Card(i) );
    }

I have a feeling that there's a clearer way than introducing a new loop variable counting from 0 to 1, especially since it isn't used except for counting.

    for ( int i = 0; i < num_pairs; i++ )
        for ( int j = 0; j < 2; j++ )
            cards.push_back( Card(i) );

(Card is just some class I made up and not relevant to the question.)

Andreas
  • 6,820
  • 8
  • 45
  • 69
  • 2
    What, exactly, do you mean by *idiom*? What kind of answer are you looking for? –  May 22 '12 at 19:27
  • value readability and maintainability – jglouie May 22 '12 at 19:30
  • I don't see anything wrong with the first example. Idioms are useful for more complex things and are meant to make programs more understandable and correct. What you're asking is a fairly straight-forward thing, there doesn't need to be an idiom for it. – Paul Manta May 22 '12 at 19:30
  • If you had a vector or something like that I would recommend you to use `std::for_each(start, end, []() {cards.push_back(Card(i));});` – Léo May 22 '12 at 19:36
  • Check the last example in this page: http://www.sgi.com/tech/stl/for_each.html – Léo May 22 '12 at 19:39
  • 1
    You could say `for (int dummy : { 0, 1 }) { /* do stuff */ }`. – Kerrek SB May 22 '12 at 19:41

5 Answers5

10

You probably want to use the fill_n function in <algorithm>

for ( int i = 0; i < num_pairs; i++ )
    std::fill_n(std::back_inserter(cards), 2, Card(i));
Collin Dauphinee
  • 12,840
  • 1
  • 36
  • 59
6

Personally I would just leave it how it is. It looks clean, it's easy to understand, and it's quite readable. Just leave a comment mentioning why you do it twice (but you would do that no matter what).

rekh127
  • 139
  • 1
  • 10
5

I've got a few suggestions. See the last for my recommendation:

  1. In my opinion insert(it, N, value) beats std::fill_n:

    for ( int i = 0; i < num_pairs; i++ ) {
        cards.insert(cards.end(), 2, Card(i) );
    }
    
  2. If order isn't important, you could just generate the cards once, and duplicate after the fact

    std::copy(cards.begin(), cards.end(), std::back_inserter(cards));
    
  3. Using a dirty lambda and integer division trick. Warning this has the hallmark of a premature optimization: reduces readability for no good reason.

    std::vector<Card> cards(num_pairs * 2);
    int i = 0;
    std::generate_n(cards.begin(), num_pairs, [&i] () { return Card(i++/2); });
    

    (assumes Card is default-constructible. If not, use cards.back_inserter())

  4. My recommendation

    The following wins in both performance and expression of intent:

    std::vector<Card> cards;
    cards.reserve(num_pairs*2);
    for (int i = 0; i < num_pairs; ++i)
    {
        cards.emplace_back(i);
        cards.emplace_back(i);
    }
    
sehe
  • 328,274
  • 43
  • 416
  • 565
1

You are right about using expanding the inner loop into statements as using another loop only 2 iterations is going to be bad for performance. Due of the frequent jumps, the second one (nested loops) is going to perform slowly. Whether there will be any noticeable difference completely depends on the num_pairs.

So the first one is better for performance(however marginal the gain might be). This sort of expanding loops is called loop unwinding/unrolling in compiler's terminology. However, this term is not used at programming level as usually only compiler does that to make the code faster. Idioms are certain design notions that help programmers write better & efficient code and understand the language better.

P.P
  • 106,931
  • 18
  • 154
  • 210
1

If you want to do this very often, write a little utility function:

template<class F>
void repeat(unsigned times, F callback) {
  while (times--) callback();
}

// ...

for (int i = 0; i < num_pairs; i++) {
  repeat(2, [&] { cards.push_back(Card(i)); });
}

I wrote an example on Ideone.


Your first approach might be confusing to future readers of your code. They might think that the code was there twice by accident. Using a function like this avoids this confusing.

The performance hit would be very minimal, if even > 0, since the compiler is likely to inline the function and completely optimize out the loop for small times'. If you're worried about performance, benchmark first.