Safer Array Length

  • Thread starter Frederick Gotham
  • Start date
F

Frederick Gotham

Many people use a method quite akin to the following to determine an
array's length:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

I recall reading an article written by Alf P. Steinbach which listed
several methods of determining an array's length. He listed the pro's and
con's of each.

One of the con's of the above method is that the macro can't detect whether
it is erronously supplied with a pointer instead of an array. (It also
evaluates "arr" twice, which is bad if we call a function which returns an
array by reference!).

In an attempt to overcome the former shortcoming, I've cooked up the
following. It's not a finished product, but you get the idea:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

template <int positive_or_negative>
struct VerifyType {
unsigned must_not_be_negative : positive_or_negative;
/* Can't have negative bit field */
};

#define VERIFY(compile_time_constant) \
( sizeof(VerifyType< \
(compile_time_constant) ? 1 : -1 \

#define ARRLEN(arr) ((void)VERIFY(NUMELEM(arr) > 1), NUMELEM(arr))

#include <iostream>
using std::cout;

int main()
{
int array[5];

int *p = array;

cout << ARRLEN(array) << '\n';

cout << ARRLEN(p) << '\n'; /* Compiler ERROR */
}

In essence, it simply performs a compile-time assert to ensure that NUMELEM
(arr) is greater than one. Of course, we'll have technicalities such as
arrays whose length is 1.
 
M

Mike Wahler

Frederick Gotham said:
Many people use a method quite akin to the following to determine an
array's length:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

I recall reading an article written by Alf P. Steinbach which listed
several methods of determining an array's length. He listed the pro's and
con's of each.

One of the con's of the above method is that the macro can't detect
whether
it is erronously supplied with a pointer instead of an array. (It also
evaluates "arr" twice, which is bad if we call a function which returns an
array by reference!).

In an attempt to overcome the former shortcoming, I've cooked up the
following. It's not a finished product, but you get the idea:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

template <int positive_or_negative>
struct VerifyType {
unsigned must_not_be_negative : positive_or_negative;
/* Can't have negative bit field */
};

#define VERIFY(compile_time_constant) \
( sizeof(VerifyType< \
(compile_time_constant) ? 1 : -1 \

#define ARRLEN(arr) ((void)VERIFY(NUMELEM(arr) > 1), NUMELEM(arr))

#include <iostream>
using std::cout;

int main()
{
int array[5];

int *p = array;

cout << ARRLEN(array) << '\n';

cout << ARRLEN(p) << '\n'; /* Compiler ERROR */
}

In essence, it simply performs a compile-time assert to ensure that
NUMELEM
(arr) is greater than one. Of course, we'll have technicalities such as
arrays whose length is 1.

Why not eliminate the above described problems as well as those
will macros in general? Use a vector instead, it 'knows' its size.

std::vector<int> v(10);
v.push_back(0);
std::cout << v.size() << '\n'; // prints 11

-Mike
 
F

Frederick Gotham

Frederick Gotham posted:
#define VERIFY(compile_time_constant) \
( sizeof(VerifyType< \
(compile_time_constant) ? 1 : -1 \

#define ARRLEN(arr) ((void)VERIFY(NUMELEM(arr) > 1), NUMELEM(arr))


I'll relocate that "void" cast:

#define VERIFY(compile_time_constant) \
( (void)sizeof(VerifyType< \
(compile_time_constant) ? 1 : -1 \

#define ARRLEN(arr) (VERIFY(NUMELEM(arr) > 1), NUMELEM(arr))
 
F

Frederick Gotham

Frederick Gotham posted:
I'll relocate that "void" cast:


I've left a few necessary parentheses out along the way. I'll re-write the
whole thing:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

template <int positive_or_negative>
struct VerifyType {
unsigned must_not_be_negative : positive_or_negative;
/* Can't have negative bit field */
};

#define VERIFY(compile_time_constant) \
( (void)sizeof(VerifyType< \
(compile_time_constant) ? 1 : -1 \

#define ARRLEN(arr) (VERIFY(NUMELEM((arr)) > 1), NUMELEM((arr)))

#include <iostream>
using std::cout;

int main()
{
int array[5];

int *p = array;

cout << ARRLEN(array) << '\n';

cout << ARRLEN(p) << '\n'; /* Compiler ERROR */
}
 
R

red floyd

Frederick said:
Many people use a method quite akin to the following to determine an
array's length:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

I recall reading an article written by Alf P. Steinbach which listed
several methods of determining an array's length. He listed the pro's and
con's of each.

One of the con's of the above method is that the macro can't detect whether
it is erronously supplied with a pointer instead of an array. (It also
evaluates "arr" twice, which is bad if we call a function which returns an
array by reference!).

In an attempt to overcome the former shortcoming, I've cooked up the
following. It's not a finished product, but you get the idea:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

template <int positive_or_negative>
struct VerifyType {
unsigned must_not_be_negative : positive_or_negative;
/* Can't have negative bit field */
};

#define VERIFY(compile_time_constant) \
( sizeof(VerifyType< \
(compile_time_constant) ? 1 : -1 \

#define ARRLEN(arr) ((void)VERIFY(NUMELEM(arr) > 1), NUMELEM(arr))

#include <iostream>
using std::cout;

int main()
{
int array[5];

int *p = array;

cout << ARRLEN(array) << '\n';

cout << ARRLEN(p) << '\n'; /* Compiler ERROR */
}

Why bother with macros at all?

template <typename T, int N>
int ARRLEN(const T(&)[N]) { return N; }

This will ONLY work with arrays, it won't work with anything else, even
if it defines a subscript operator, unlike the macros.
 
R

Roland Pibinger

Why bother with macros at all?

template <typename T, int N>
int ARRLEN(const T(&)[N]) { return N; }

This will ONLY work with arrays, it won't work with anything else, even
if it defines a subscript operator, unlike the macros.

Nice solution without any macro. Some (minor) drawbacks are:
- it doesn't work for locally defined T
- it needs a "newer" compiler
- the error message may confuse people

But for the typical usage it should be the preferred solution,
especially since it avoids any macro. Even

int array2[ARRLEN(array)];

seems to work.

Best wishes,
Roland Pibinger
 
F

Frederick Gotham

Roland Pibinger posted:
Negligible, IMO

#include <cstddef>

template<class T,std::size_t len>
std::size_t ArrLen(T const (&)[len])
{
return len;
}

int main()
{
struct MyClass {
int i;
};

MyClass array[10];

ArrLen(array);
}
 
O

Old Wolf

Frederick said:
Many people use a method quite akin to the following to determine an
array's length:

#define NUMELEM(arr) (sizeof (arr) / sizeof *(arr))

It also evaluates "arr" twice, which is bad if we call a function which
returns an array by reference!

It evaluates "arr" zero times. The sizeof operator does not
evaluate its argument.
 
F

Frederick Gotham

Old Wolf posted:
It evaluates "arr" zero times. The sizeof operator does not
evaluate its argument.


You're correct -- don't know how I missed that.

Also, one doesn't need to actually INVOKE a functon to know the size of the
array it returns:

#include <iostream>
using std::cout;

int (&Func())[5]
{
int static arr[5] = {};

return arr;
}

int main()
{
cout << sizeof Func() / sizeof *Func();
}
 
R

Roland Pibinger

Why bother with macros at all?

template <typename T, int N>
int ARRLEN(const T(&)[N]) { return N; }
[...]
Even

int array2[ARRLEN(array)];

seems to work.

Actually, the above doesn't work. It only happend to compile with one
compiler.
 
F

Frederick Gotham

Roland Pibinger posted:
template <typename T, int N>
int ARRLEN(const T(&)[N]) { return N; }
[...]
Even

int array2[ARRLEN(array)];

seems to work.

Actually, the above doesn't work. It only happend to compile with one
compiler.


The invocation of a function NEVER results in a compile-time constant
(althought I believe this is going to change with the next standard).

int inline Func()
{
return 5;
}

int main()
{
int array[Func()]; /* Compile ERROR */
}
 
H

Howard

Frederick Gotham said:
Roland Pibinger posted:
Negligible, IMO

#include <cstddef>

template<class T,std::size_t len>
std::size_t ArrLen(T const (&)[len])
{
return len;
}

int main()
{
struct MyClass {
int i;
};

MyClass array[10];

ArrLen(array);
}

You gonna make me copy & compile that to get your point? A little
explanation of what you're trying to show would help...

Personally, I can't recall a time I've ever defined a class local to a
function. Why would I need to do that? Move the definition outside the
function.

Come to think of it, I've never needed to use a trick like this to get an
array's size. If it's important to me, I just keep track myself of the size
of arrays I allocate, or else I use vector. Is there really ever a case
where that information isn't available, except in the case of a flaw in the
original design (i.e., failing to account for a _need_ to keep track of the
size)?

-Howard
 
R

Roland Pibinger

Roland Pibinger posted:
template <typename T, int N>
int ARRLEN(const T(&)[N]) { return N; } [...]
Even

int array2[ARRLEN(array)];

seems to work.

Actually, the above doesn't work. It only happend to compile with one
compiler.

The invocation of a function NEVER results in a compile-time constant
(althought I believe this is going to change with the next standard).

The above is not a function but a function template which may make a
difference in that case. At least g++ 3.4 (falsely) compiled it.

Best wishes,
Roland Pibigner
 
B

Bo Persson

Howard said:
MyClass array[10];

ArrLen(array);
}


Come to think of it, I've never needed to use a trick like this to
get an array's size. If it's important to me, I just keep track
myself of the size of arrays I allocate, or else I use vector. Is
there really ever a case where that information isn't available,
except in the case of a flaw in the original design (i.e., failing
to account for a _need_ to keep track of the size)?

Right. How do you allocate an array, if you don't know the size of it
before hand? :)

The real problem is using the literal 10 in the code.


Bo Persson
 
F

Frederick Gotham

Roland Pibinger posted:
The above is not a function but a function template which may make a
difference in that case. At least g++ 3.4 (falsely) compiled it.


A template for a function becomes a function when initialised (or
specialised?).

As far as the current Standard is concerned, it shouldn't yield a compile-
time constant.
 
T

Thomas J. Gritzan

Roland said:
Roland Pibinger posted:
template <typename T, int N>
int ARRLEN(const T(&)[N]) { return N; }
[...]
Even

int array2[ARRLEN(array)];

seems to work.
Actually, the above doesn't work. It only happend to compile with one
compiler.
The invocation of a function NEVER results in a compile-time constant
(althought I believe this is going to change with the next standard).

The above is not a function but a function template which may make a
difference in that case. At least g++ 3.4 (falsely) compiled it.

g++ compiled it, because it has VLA as an extension.

One could implement a templated struct to get a compile time constant.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top