While refactoring code and ridding myself of all those #defines that we\'re now taught to hate, I came across this beauty used to calculate the number of elements in a struc
As JohnMcG's answer, but
Disadvantage is you will have a copy of this in your binary for every Typename, Size combination.
That's why you'd make it an inline template function.
I don't think that that really does work out the number of elements in a structure. If the structure is packed and you used things smaller than the pointer size (such as char on a 32-bit system) then your results are wrong. Also, if the struct contains a struct you are wrong too!
None has so far proposed a portable way to get the size of an array when you only have an instance of an array and not its type. (typeof and _countof is not portable so can't be used.)
I'd do it the following way:
template<int n>
struct char_array_wrapper{
char result[n];
};
template<typename T, int s>
char_array_wrapper<s> the_type_of_the_variable_is_not_an_array(const T (&array)[s]){
}
#define ARRAYSIZE_OF_VAR(v) sizeof(the_type_of_the_variable_is_not_an_array(v).result)
#include <iostream>
using namespace std;
int main(){
int foo[42];
int*bar;
cout<<ARRAYSIZE_OF_VAR(foo)<<endl;
// cout<<ARRAYSIZE_OF_VAR(bar)<<endl; fails
}
I wrapped the construct into a macro to have some decent syntax. If you want to get rid of it your only option is to do the substitution manually.
Your macro is misnamed, it should be called ARRAYSIZE. It is used to determine the number of elements in an array whos size is fixed at compile time. Here's a way it can work:
char foo[ 128 ]; // In reality, you'd have some constant or constant expression as the array size.
for( unsigned i = 0; i < STRUCTSIZE( foo ); ++i ) { }
It's kind of brittle to use, because you can make this mistake:
char* foo = new char[128];
for( unsigned i = 0; i < STRUCTSIZE( foo ); ++i ) { }
You will now iterate for i = 0 to < 1 and tear your hair out.
The type of a template function is inferred automatically, in contrast with that of a template class. You can use it even simpler:
template< typename T > size_t structsize( const T& t ) {
return sizeof( t ) / sizeof( *t );
}
int ints[] = { 1,2,3 };
assert( structsize( ints ) == 3 );
But I do agree it doesn't work for structs: it works for arrays. So I would rather call it Arraysize :)
KTC's solution is clean but it can't be used at compile-time and it is dependent on compiler optimization to prevent code-bloat and function call overhead.
One can calculate array size with a compile-time-only metafunction with zero runtime cost. BCS was on the right track but that solution is incorrect.
Here's my solution:
// asize.hpp
template < typename T >
struct asize; // no implementation for all types...
template < typename T, size_t N >
struct asize< T[N] > { // ...except arrays
static const size_t val = N;
};
template< size_t N >
struct count_type { char val[N]; };
template< typename T, size_t N >
count_type< N > count( const T (&)[N] ) {}
#define ASIZE( a ) ( sizeof( count( a ).val ) )
#define ASIZET( A ) ( asize< A >::val )
with test code (using Boost.StaticAssert to demonstrate compile-time-only usage):
// asize_test.cpp
#include <boost/static_assert.hpp>
#include "asize.hpp"
#define OLD_ASIZE( a ) ( sizeof( a ) / sizeof( *a ) )
typedef char C;
typedef struct { int i; double d; } S;
typedef C A[42];
typedef S B[42];
typedef C * PA;
typedef S * PB;
int main() {
A a; B b; PA pa; PB pb;
BOOST_STATIC_ASSERT( ASIZET( A ) == 42 );
BOOST_STATIC_ASSERT( ASIZET( B ) == 42 );
BOOST_STATIC_ASSERT( ASIZET( A ) == OLD_ASIZE( a ) );
BOOST_STATIC_ASSERT( ASIZET( B ) == OLD_ASIZE( b ) );
BOOST_STATIC_ASSERT( ASIZE( a ) == OLD_ASIZE( a ) );
BOOST_STATIC_ASSERT( ASIZE( b ) == OLD_ASIZE( b ) );
BOOST_STATIC_ASSERT( OLD_ASIZE( pa ) != 42 ); // logic error: pointer accepted
BOOST_STATIC_ASSERT( OLD_ASIZE( pb ) != 42 ); // logic error: pointer accepted
// BOOST_STATIC_ASSERT( ASIZE( pa ) != 42 ); // compile error: pointer rejected
// BOOST_STATIC_ASSERT( ASIZE( pb ) != 42 ); // compile error: pointer rejected
return 0;
}
This solution rejects non-array types at compile time so it will not get confused by pointers as the macro version does.