This set of slides introduces the reader to the concept of multidimensional arrays in C++ (with elements of C++11 and C++14). The true nature of multidimensional arrays is discussed by means of an intermediate type alias. The pheonomenon of array-to-pointer decay and pointer arithmetic is then generalized to arrays of arrays. The presentation proceeds with a discussion on how to pass multidimensional arrays to functions.
2. Outline
• Arrays of arrays
• Arrays of arrays vs. pointers
• Pointer arithme4c
• Arrays of arrays vs. func4ons
• The array of arrays type
• Bibliography
7. Type aliases
C++ supports type aliases, i.e., new names for previously defined
types1
using integer = int;
integer a = 5; // the same as: int a = 5
1
Star'ng from C++11, type aliases can be introduced by means of the using keyword (in addi'on to the already
available typedef keyword)
8. Type aliases for array types
We can introduce type aliases for array types as well.
For instance, let us introduce a type alias for the type int[4]
using int4 = int[4];
9. Type aliases for array types
We can now define numbers in terms of the type alias int4
using int4 = int[4];
int4 numbers = {1, 7, 13, 10};
10. Can we now take advantage of our newly-defined alias to define an
array of, e.g., 3 elements of type int4?
16. Ini$alizing an array
We know that arrays can be ini1alized using an ini#alizer list
int numbers[4] = {1, 7, 13, 10};
17. Ini$alizing an array
In case of numbers, the ini.alizer list is composed of int values
// 1 is a int, 7 is a int, ...
int numbers[4] = {1, 7, 13, 10};
18. Ini$alizing an array of arrays
In case of m, the ini.alizer list is composed of int4 values
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2}, // {1, 7, 14, 2} is an int4
{8, 16, 12, 10}, // {8, 16, 12, 10} is an int4
{27, 32, 5, 15} }; // {27, 32, 5, 15} is an int4
19. Accessing inner elements
It would be convenient to be able to access inner elements in m
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
20. Accessing inner elements
For example, being able to access the element in (2, 1)
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
21. Accessing inner elements
In order to do so, it is sufficient to specify the right amount of
indices (as many as the number of dimensions)
m[2][1]
22. Accessing inner elements
In order to do so, it is sufficient to specify the right amount of
indices (as many as the number of dimensions)
m[2][1] → {27, 32, 5, 15}[1]
23. Accessing inner elements
In order to do so, it is sufficient to specify the right amount of
indices (as many as the number of dimensions)
m[2][1] → {27, 32, 5, 15}[1] → 32
27. Mul$dimensional arrays
For common arrays, elements are addressed by specifying one
index
┌───┬───┬───┬───┐
│ │ │ │ │
└───┴───┴───┴───┘
▲
│
i
28. Mul$dimensional arrays
For mul(dimensional arrays, each element is iden(fied by a tuple of
indexes (as many as the number of dimensions)
┌───┬───┬───┬───┐
│ │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ │
├───┼───┼───┼───┤
│ │ │ │ │◀─── i
└───┴───┴───┴───┘
▲
│
j
29. Arrays of arrays
C++ does not provide proper mul1dimensional arrays. Instead,
mul1dimensional arrays are approximated as arrays of arrays
30. Memory layout
Since mul*dim. arrays are approximated as arrays of arrays, it
follows that elements are organized in memory as a sequence
m[0] m[1] m[2]
◀──────────────▶◀────────────▶◀─────────────▶
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
m: │ │ │ │ │ │ │ │ │ │ │ │ │ int4[3]
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
32. Pointer to first element
Assume we want to store the address of the first element of m
// {1, 7, 14, 2} is the 1st element
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
33. Pointer to first element
We start by no,cing that the element type of m is int4
int4 m[3] = { {1, 7, 14, 2}, // {1, 7, 14, 2} is an int4
{8, 16, 12, 10}, // {8, 16, 12, 10} is an int4
{27, 32, 5, 15} }; // {27, 32, 5, 15} is an int4
34. Pointer to first element
Since the element type is equal to int4, a pointer2
to any element
in the array has to be of type int4*
using int4 = int[4];
int4* first = ...
2
The int4 type alias provides a convenient way to introduce pointers to arrays. Without the intermediate type alias,
one would define first to be of type int(*)[4], which is arguably less readable
35. What to write on the RHS?
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
int4* first = ???
36. What to write on the RHS?
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
int4* first = &m[0]; // the address of {1, 7, 14, 2}
37. Pointer decay
As with any array, pointer decay can be used
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
// array-to-pointer decay (int4[3] → int4*)
int4* first = m;
40. Pointer arithme,c
Given that arrays of arrays are only a par1cular case of arrays,
pointer arithme,c can be applied on them without any special
considera1on
41. Pointer arithme,c: addi,on
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
int4* first = m; // ptr to {1, 7, 14, 2}
int4* second = m + 1; // ptr to {8, 16, 12, 10}
int4* third = m + 2; // ptr to {27, 32, 5, 15}
42. Pointer arithme,c
No#ce that pointer arithme#c allows moving between the rows of
our 2-dimensional array m
int4* first = m; // ptr to {1, 7, 14, 2}
int4* second = m + 1; // ptr to {8, 16, 12, 10}
int4* third = m + 2; // ptr to {27, 32, 5, 15}
43. Deriving the indexing operator
Let us now try to reduce inner element access to a sequence of
pointer arithme,c opera,ons
44. Deriving the indexing operator
To this end, let us consider the following expression:
m[2][1]
45. Deriving the indexing operator
By replacing [1] with its defini3on we obtain:
m[2][1] → *(m[2] + 1)
46. Deriving the indexing operator
If we do the same for [2] we obtain:
m[2][1] → *(m[2] + 1) → *(*(m + 2) + 1)
47. Indexing operator on arrays of arrays
Accessing inner elements of a array of arrays is therefore s2ll
defined in terms of pointer arithme,c
Given an array of arrays m, and a pair of indexes i and j, the
opera3on a[i][j] is implemented as *(*(m + i) + j)
49. The sum_int func)on
Assume we would like to write a func3on named sum_int, which
computes the summa3on of all the elements in a 2-dimensional
array
50. What we know
• We can model 2-dimensional arrays as arrays of arrays
• Arrays of arrays follow the same rules as normal arrays
• We cannot pass arrays by value
• The common workaround is to design func>ons that accept a
pointer to the 1st
element, together with the array size
51. An interface for sum_int
As a first step, let us write sum_int so that it works properly when
m is fed as an input
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
53. An interface for sum_int
int sum_int(int4* first, std::size_t n);
54. An interface for sum_int
Since we want sum_int to work with m, we impose first to be of
type int4
int sum_int(int4* first, std::size_t n);
55. An interface for sum_int
Hence, sum_int can only work with 2-dimensional arrays of
exactly 4 columns
int sum_int(int4* first, std::size_t n);
56. An interface for sum_int
int sum_int(int4* first, std::size_t n);
• The number of rows is determined by the parameter n
• The number of columns is fixed by the type of first (int4)
57. An interface for sum_int
If we need to work with 2-dimensional arrays of, e.g., 5 columns,
we need to write a new func(on4
int sum_int(int4* first, std::size_t n);
int sum_int5(int5* first, std::size_t n);
4
This can be automated by using a func%on template
58. sum_int → sum_int4
In light of this, let us rename sum_int as sum_int4
int sum_int4(int4* first, std::size_t n);
59. Implemen'ng sum_int4
int sum_int4(int4* first, std::size_t n) {
int sum = 0;
for (std::size_t i = 0; i < n; ++i)
for (std::size_t j = 0; j < 4; ++j)
sum += ???
return sum;
}
60. Implemen'ng sum_int4
int sum_int4(int4* first, std::size_t n) {
int sum = 0;
for (std::size_t i = 0; i < n; ++i)
for (std::size_t j = 0; j < 4; ++j)
sum += *(*(first + i) + j);
return sum;
}
61. Implemen'ng sum_int4
int sum_int4(int4* first, std::size_t n) {
int sum = 0;
for (std::size_t i = 0; i < n; ++i)
for (std::size_t j = 0; j < 4; ++j)
sum += first[i][j];
return sum;
}
62. Invoking sum_int4
int main() {
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
int r = sum_int4(???, ???);
std::cout << "the elements sum up to " << r;
}
63. Invoking sum_int4
int main() {
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
int r = sum_int4(m, 3);
std::cout << "the elements sum up to " << r;
}
65. The int4 type alias
Up to now, we took advantage of the int4 type alias for the
defini7on of m
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
66. The int4 type alias
We did so in order to stress the true nature of mul1dimensional
arrays in C++
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
67. The data type of m
A"er introducing int4, we can say that m is of type int4[3]
(i.e., an array of 3 elements of type int4)
using int4 = int[4];
int4 m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
68. The data type of m
However, one could wonder how to express the data type of m
without introducing the int4 type alias
??? m[3] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
69. The data type of m
This amounts to asking what is the result of applying the [3] type
constructor on int[4]
70. Applying [3] on int
Construct a type "array of 3 elements of type int"
Input type int
Type constructor: [3]
Output type: int[3]
71. Applying [3] on int[4]
Construct a type "array of 3 elements of type int[4]"
Input type: int[4]
Type constructor: [3]
Output type: int[3][4]
72. The data type of m
Therefore, we conclude that m is of type int[3][4]
int m[3][4] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
73. Arrays of arrays
The common C++ syntax for defining arrays of arrays gives the
illusion of working with mul;dimensional arrays
int m[3][4] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
74. Arrays of arrays
However - though the syntax would suggest otherwise - the
element type of m is s8ll int[4], not int
int m[3][4] = { {1, 7, 14, 2},
{8, 16, 12, 10},
{27, 32, 5, 15} };
75. The alterna*ve signature
As with normal arrays, C++ admits func6on signatures that may
appear more natural when dealing with mul6dimensional arrays
78. The alterna*ve signature
int sum_int4(int m[][4], std::size_t n) {
int sum = 0;
for (std::size_t i = 0; i < n; ++i)
for (std::size_t j = 0; j < 4; ++j)
sum += m[i][j];
return sum;
}
80. Bibliography
• S.B. Lippman et al., C++ Primer (5th
Ed.)
• B. Stroustrup, The C++ Programming Language (4th
Ed.)
• StackOverflow FAQ, "How do I use arrays in C++?"