How Does This Template Magic Determine Array Parameter Size

How does this template magic determine array parameter size?

N does not get "initialized" to anything. It is not a variable. It is not an object. N is a compile-time constant. N only exists during compilation. The value of N as well as the actual T is determined by the process called template argument deduction. Both T and N are deduced from the actual type of the argument you pass to your template function.

In the first call the argument type is int[6], so the compiler deduces that T == int and N == 6, generates a separate function for that and calls it. Let's name it cal_size_int_6

void cal_size_int_6(int (&a)[6]) 
{
std::cout << "size of array is: " << 6 << std::endl;
}

Note that there's no T and no N in this function anymore. Both were replaced by their actual deduced values at compile time.

In the first call the argument type is int[1], so the compiler deduces that T == int and N == 1, generates a separate function for that as well and calls it. Let's name it cal_size_int_1

void cal_size_int_1(int (&a)[1]) 
{
std::cout << "size of array is: " << 1 << std::endl;
}

Same thing here.

Your main essentially translates into

int main() 
{
int a[]={1,2,3,4,5,6};
int b[]={1};

cal_size_int_6(a);
cal_size_int_1(b);
}

In other words, your cal_size template gives birth to two different functions (so called specializations of the original template), each with different values of N (and T) hardcoded into the body. That's how templates work in C++.

Can someone explain this template code that gives me the size of an array?

Well, first you have to understand that trying to get a value out of an array can give you a pointer to its first element:

int a[] = {1, 2, 3};
int *ap = a; // a pointer, size is lost
int (&ar)[3] = a; // a reference to the array, size is not lost

References refer to objects using their exact type or their base-class type. The key is that the template takes arrays by reference. Arrays (not references to them) as parameters do not exist in C++. If you give a parameter an array type, it will be a pointer instead. So using a reference is necessary when we want to know the size of the passed array. The size and the element type are automatically deduced, as is generally the case for function templates. The following template

template<typename T, size_t n>
size_t array_size(const T (&)[n]) {
return n;
}

Called with our previously defined array a will implicitly instantiate the following function:

size_t array_size(const int (&)[3]) {
return 3;
}

Which can be used like this:

size_t size_of_a = array_size(a);

There's a variation I made up some time ago [Edit: turns out someone already had that same idea here] which can determine a value at compile time. Instead of returning the value directly, it gives the template a return type depending on n:

template<typename T, size_t n>
char (& array_size(const T (&)[n]) )[n];

You say if the array has n elements, the return type is a reference to an array having size n and element type char. Now, you can get a compile-time determined size of the passed array:

size_t size_of_a = sizeof(array_size(a));

Because an array of char having n elements has sizeof n, that will give you the number of elements in the given array too. At compile time, so you can do

int havingSameSize[sizeof(array_size(a))];

Because the function never is actually called, it doesn't need to be defined, so it doesn't have a body. Hope I could clear the matter up a little bit.

How does this Array Size Template Work?

The function template is named ArraySizeHelper, for a function that takes one argument, a reference to a T [N], and returns a reference to a char [N].

The macro passes your object (let's say it's X obj[M]) as the argument. The compiler infers that T == X and N == M. So it declares a function with a return type of char (&)[M]. The macro then wraps this return value with sizeof, so it's really doing sizeof(char [M]), which is M.

If you give it a non-array type (e.g. a T *), then the template parameter inference will fail.

As @Alf points out below, the advantage of this hybrid template-macro system over the alternative template-only approach is that this gives you a compile-time constant.

How does sizeof work when passing a template array?

If you are asking "how does the compiler know to put the array size into n"... The expression

const T (&arr)[n]

is being passed

int arr[11]

hence it is able to deduce T is int and n is 11.

If you are asking how it knows how large arr is...

int arr[] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21 };
cout << isInHaystack(arr, 7) << endl;

arr is an array. The compiler knows how big it is. If you are thinking "arr is really just a pointer", that's not true. Arrays and pointers are said to have equivalence (see K&R section 5.3), which doesn't mean they are same, but that they result in the same behavior in a limited number of contexts.

In C and C++ arrays are able to decay into pointers, but they are still not pointers until the decay occurs.

int arr[] = { 1, 3, 5, 7 };
int* arrp = arr; // decay
cout << isInHaystack(arr, 7); // Error.

See http://c-faq.com/aryptr/aryptrequiv.html

How do I get the exact length of an array without using subscripted values within a template function in C++?

Here's a solution that might work for you. It uses if constexpr to ensure that the first template only tries to compile valid branches in the code (you can add more if you need them) and a template overload to handle arrays of arbitrary type. Note: needs C++17 or later.

#include <iostream>
#include <cstring>
#include <type_traits>

template <typename T>
size_t sizeOf (T val)
{
size_t sz = 0;

if constexpr (std::is_integral_v <T>)
sz = sizeof (val);
else if constexpr (std::is_same_v <T, const char *>)
sz = strlen (val);

return sz;
}

template <typename T, size_t size>
size_t sizeOf (T (&)[size])
{
return size;
}

int main ()
{
int i = 0;
char c = 0;
std::cout << "sizeOf int = " << sizeOf (i) << "\n";
std::cout << "sizeOf char = " << sizeOf (c) << "\n";
const char *pc = "abcde";
std::cout << "sizeOf abcde = " << sizeOf (pc) << "\n";
int a [6];
std::cout << "sizeOf int [6] = " << sizeOf (a) << "\n";
}

Output:

sizeOf int = 4
sizeOf char = 1
sizeOf abcde = 5
sizeOf int [6] = 6

Receive variadic sized raw arrays of the same type in a C++ template function

Knowing where to put the ... is much simpler with a type alias

template<std::size_t N>
using chars = const char (&)[N];

template<std::size_t... SIZES>
constexpr std::array<const char*, sizeof...(SIZES)> fun(chars<SIZES>... names)
{
return { names... };
}

See it on coliru



Related Topics



Leave a reply



Submit