On 31.10.2018 21:16, Richard wrote:
> [Please do not mail me a copy of your followup]
>
> "Alf P. Steinbach" <
alf.p.stein...@gmail.com> spake the secret code
> <prch1f$1c7$
1...@dont-email.me> thusly:
>
>> Then you'd discover that the following way of writing the loop,
>>
>> for( int i = 2, n = cards.size(); i < n; ++i )
>>
>> ... both avoids warnings and guarantees performance.
>
> Is the performance claim really legitimate?
It guarantees a single call of the `size` function. The compiler might
be able to do that on its own, or not. There's no guarantee of
optimization of the original loop, because it depends on whether the
compiler is smart enough to see that no operation modifies the vector.
> Is there any compiler that isn't going to lift this loop
> invariant out of the loop for you when optimizations are turned on?
Don't know, sorry.
> I find doing this just makes the code less clear
I seldom find naming to make code less clear, provided the names are
reasonably descriptive. On the contrary, not naming things tends to
create an unclear mess. `n` is self-explanatory to my eyes, YMMV. :)
> and introduces a
> variable that can be accidentally (or "cleverly") modified when you
> didn't intent for it to be modified.
One can make `n` const if it's declared before the loop.
Technically one can do that also within the loop, but it's ugly.
I coded up some examples:
#include <stddef.h> // ptrdiff_t
template< class Type >
auto dummy_use( const Type& ) -> bool { return true; }
#define $with( ... ) if( const auto& _ = __VA_ARGS__; dummy_use( _ ) ||
true )
template< class Container >
auto n_items( const Container& c )
-> ptrdiff_t
{ return size( c ); }
struct Iter{ const int n; int i; };
class Up_to
{
const int my_n;
public:
template< class F >
void perform( const F& f )
{
for( int i = 0; i < my_n; ++i ) { f( i ); }
}
Up_to( const int n ): my_n( n ) {}
};
#include <iostream>
#include <vector>
#include <iterator> // std::size
using namespace std;
auto main()
-> int
{
const vector<int> numbers = {1, 2, 3, 4, 5};
// Original code, just modified to avoid signed/unsigned warning/bugs.
for( int i = 0; i < n_items( numbers ); ++i )
{
cout << numbers[i] << ' ';
}
cout << endl;
// Near idiomatic.
for( int i = 0, n = n_items( numbers ); i < n; ++i )
{
cout << numbers[i] << ' ';
}
cout << endl;
// "Safe" version of the above loop. But IMHO it's just silly.
for( int i = 0, n = n_items( numbers ); i < n; ++i )
{
void n(); // The safety, code below can't change `n`.
cout << numbers[i] << ' ';
}
cout << endl;
// Safe and conventional details, but "leaks" `n` to rest of scope.
const int n = n_items( numbers );
for( int i = 0; i < n; ++i )
{
cout << numbers[i] << ' ';
}
cout << endl;
// Addresses the leakage of a no-purpose name by using "_" as name.
$with( n_items( numbers ) ) for( int i = 0; i < _; ++i )
{
cout << numbers[i] << ' ';
}
cout << endl;
// A silly way to have `n` const and all variables local to the loop.
for( Iter L{ int( n_items( numbers ) ) }; L.i < L.n; ++L.i )
{
cout << numbers[L.i] << ' ';
}
cout << endl;
// Not so sure if this is silly, over-engineering, or actually useful?
Up_to( n_items( numbers ) ).perform( [&]( const int i )
{
cout << numbers[i] << ' ';
} );
cout << endl;
cout << "Finished!" << endl;
}
Cheers!,
- Alf