std::begin
and std::end
know the beginning and end of a container
or an array
.
It so easy to know the end
Because you are passing an array to std::end
, and an array has type T [N]
. std::end
can tell when the array ends by looking at the N
in the type.
But, how does it know the end of an array
It uses a template non-type parameter to deduce the size of the array, which can then be used to produce the end pointer. The C++11 signature from the cppreference section for std::end is as follows:
template< class T, std::size_t N >
T* end( T (&array)[N] );
As hvd notes, since it is passed by reference this prevents decay to a pointer.
The implementation would be something similar to:
template< class T, std::size_t N >
T* end( T (&array)[N] )
{
return array + N ;
}
Is the constant integer 5 will be stored some where?
5
or N
is part of the type of the array and so N
is available at compile time. For example applying sizeof to an array will give us the total number of bytes in the array.
Many times we see an array passed by value to a function. In that case, the array decays to a pointer to type stored in the array. So now the size information is lost. Passing by reference allows us to avoid this loss of information and extract the size N
from the type.
is the constant integer 5 will be stored some where?
Yes, it's part of the type of the array. But no, it's not stored anywhere explicitly. When you have
int i[5] = { };
the type of i
is int[5]
. Shafik's answer talks about how this length is used to implement end
.
If you've C++11, using constexpr
would be the simple way to go
template <typename T, size_t N>
inline constexpr size_t
arrLen(const T (&arr) [N]) {
return N;
}
If you've a pre-C++11 compiler where constexpr
isn't available, the above function may not be evaluated at compile-time. So in such situations, you may use this:
template <typename T, size_t N>
char (&arrLenFn(const T (&arr) [N]))[N];
#define arrLen(arr) sizeof(arrLenFn(arr))
First we declare a function returning a reference to an array of N char
s i.e. sizeof
this function would now be the length of the array. Then we've a macro to wrap it, so that it's readable at the caller's end.
Note: Two arrays of the same base type but with different lengths are still two completely different types. int[3]
is not the same as int[2]
. Array decay, however, would get you an int*
in both cases. Read How do I use arrays in C++? if you want to know more.