Wednesday 18 September 2013

C++: Template argument deduction to deduce array dimensions

If you call a template function, the template arguments can be omitted if the compiler can determine or deduce the template arguments by the context and usage of the function parameters. Here is an example...

#include <iostream>

template <class T>
void f(T arg1) {
  std::cout << arg1 << std::endl;
}

int main() {
  int a = 1;

  // The template argument is explicitly defined.
  f<int>(a);

  // The template argument is NOT explicitly defined. This usage is also
  // correct!!
  f(a);
  return 0;
}
The compiler is able to deduce the template argument T, when f() is called from the parameter a.
The compiler tries to deduce a template argument by comparing the type of the corresponding template parameter with the type of the argument used in the function call. The two types that the compiler compares (the template parameter and the argument used in the function call) must be of a certain structure in order for template argument deduction to work. A list of these type structures can be found at [1].

Non-type template parameters


The template facility in C++ doesn’t only allow you to parameterize with types (such as the int), but also with values. Non-type template parameters can be of the following types:
  • Integral (or enum) value
  • Pointer to object/function
  • Reference to object/function
  • Pointer to member

Here is example usage of non-type template parameters, obtained from [2].

template<int a[4]> struct A { };
template<int f(int)> struct B { };

int i;
int g(int) { return 0;}

A<&i> x;
B<&g> y;


Array dimension deduction


In C++, an array is passed to a function either as a pointer (int *array), an array of a defined size (int array[M][N]) or an array of an undefined size (int array [][]). In all these cases, the callee gets only the start address of the array and has no information about its size, i.e. its dimension(s). Just as in the case of type deduction, I'll try to make the compiler deduce array dimension using templates, parameterized using int. In the following examples, I'll pass a 2-D array to a function. This method can be extended to an array of any number of dimensions.

#include <iostream>

template<int M, int N>
void func(int arr[M][N]) {
  std::cout << sizeof(arr) << " " << M << " " << N << std::endl;
}

int main() {
  int arr[5][5];
  func<5, 5>(arr);
}
The above method works fine and there is nothing special as the array dimensions are passed as template arguments. The output of the above code is:
4 5 5
This is because sizeof(arr) is the size of the array pointer which is 4 bytes and not the size of the array.

Now, let's try to make the compiler deduce the array dimensions by changing func<5, 5>(arr) to func(arr). The result here is quite unexpected. I get the following compilation error:
error: no matching function for call to ‘func(int [5][5])’


What is happening is that, when the compiler instantiates arr, a conversion to pointer type takes place and the dimensionality information is lost. This is called 'decay'. 'Decay'ing makes it impossible to deduce the array dimensions using template argument deduction.

There is a simple work around. 'Decay' can be prevented by making the function argument a reference to an array, as follows..

#include <iostream>

template<int M, int N>
void func(int (&arr)[M][N]) {
  std::cout << sizeof(arr) << " " << M << " " << N << std::endl;
}

int main() {
  int arr[5][5];
  func(arr);
}
Voila! This works! We have deduced the dimensions of the array passed to the function at compile time.



[1]: http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Ftemplate_argument_deduction.htm [1]: http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fnon-type_template_parameters.htm

2 comments:

  1. Hello There,

    I genuinely do look forward for the time where you post some new write ups. Your blog make me feel so educated! Continue soaring and writing please.

    int main(int argc, char *argv[])
    {
    QCoreApplication a(argc, argv);

    int A[3][2]={{1,2},
    {4,5},
    {7,8}};

    int B[3][1]=
    {{0},
    {4,},
    {7,}};

    i want to merge array A and B into a single array C ,which should appear like

    int C= 0 1 2
    4 4 5
    7 7 8

    Thanks a lot. This was a perfect step-by-step guide. Don’t think it could have been done better.

    Thanks,
    Preethi

    ReplyDelete