C++ Pass Array By Reference VS By Pointer

Introduction

When designing a function that accepts a C array in C++, I personally would choose passing by pointer to an array almost all the time. Recently, I realized that passing the reference to an array is actually more beneficial in some use cases because we could use C++ iterators for C array and take the advantage of many STL container and algorithms.

In this blog post, I would like to quickly discuss passing a C array to a function via pointer and reference.

C++ Pass Array By Reference VS By Pointer

The major caveat of passing an array to a function is that we might have passed a pointer even though we thought we have passed a reference. The arrays such as T arr[N] and T arr[] in the function signature all decays to pointers. The correct way of passing by reference is to use T (&arr)[N].

Passing by pointer and and passing by reference to array sometimes does not make too much difference in practice. But if passing by pointer, we lost the benefits of using sizeof and C++ iterators in some use cases.

pass_array.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#include <iostream>
#include <iterator>

// C style interface
template <typename T>
void print_array_by_pointer(T* arr, size_t N)
// This is equivalent to
// template <typename T>
// void print_array_by_pointer(T arr[], size_t N)
{
// arr is a pointer.
std::cout << "Size of pointer: " << sizeof(arr) << std::endl;
std::cout << "Elements in array: " << std::endl;
// This would not work.
// std::copy(std::cbegin(arr), std::cend(arr),
// std::ostream_iterator<T>(std::cout, " "));
// Cannot get an iterator from a pointer.
for (int i{0}; i < N; ++i)
{
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}

template <typename T, int N>
void print_array_by_pointer(T arr[N])
// This is equivalent to
// template <typename T, int N>
// void print_array_by_pointer(T* arr)
// or
// template <typename T, int N>
// void print_array_by_pointer(T arr[])
{
// arr decays to a pointer.
std::cout << "Size of pointer: " << sizeof(arr) << std::endl;
std::cout << "Elements in array: " << std::endl;
// This would not work.
// std::copy(std::cbegin(arr), std::cend(arr),
// std::ostream_iterator<T>(std::cout, " "));
// Cannot get an iterator from a pointer.
for (int i{0}; i < N; ++i)
{
std::cout << arr[i] << " ";
}
std::cout << std::endl;
}

template <typename T, int N>
void print_array_by_reference(T (&arr)[N])
{
// arr is still an array of size N.
std::cout << "Size of array: " << sizeof(arr) << std::endl;
std::cout << "Elements in array: " << std::endl;
std::copy(std::cbegin(arr), std::cend(arr),
std::ostream_iterator<T>(std::cout, " "));
std::cout << std::endl;
}

template <typename T, int N>
void print_array_by_pointer_to_array(T (*arr)[N])
{
// arr is a pointer to an array of size N.
std::cout << "Size of array: " << sizeof(*arr) << std::endl;
std::cout << "Elements in array: " << std::endl;
std::copy(std::cbegin(*arr), std::cend(*arr),
std::ostream_iterator<T>(std::cout, " "));
std::cout << std::endl;
}

int main()
{
int arr[5] = {0, 1, 2, 3, 4};
print_array_by_pointer(arr, sizeof(arr) / sizeof(arr[0]));
print_array_by_pointer<std::remove_reference<decltype(arr[0])>::type,
sizeof(arr) / sizeof(arr[0])>(arr);
print_array_by_reference(arr);
// Same interface as print_array_by_pointer.
// print_array_by_reference<std::remove_reference<decltype(arr[0])>::type,
// sizeof(arr) / sizeof(arr[0])>(arr);
print_array_by_pointer_to_array(&arr);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ g++ pass_array.cpp -o pass_array -std=c++14
pass_array.cpp: In instantiation of ‘void print_array_by_pointer(T*) [with T = int; int N = 5]’:
pass_array.cpp:69:57: required from here
pass_array.cpp:29:48: warning: ‘sizeof’ on array function parameter ‘arr’ will return size of ‘int*’ [-Wsizeof-array-argument]
29 | std::cout << "Size of pointer: " << sizeof(arr) << std::endl;
| ~^~~~
pass_array.cpp:23:31: note: declared here
23 | void print_array_by_pointer(T arr[N])
| ~~^~~~~~
$ ./pass_array
Size of pointer: 8
Elements in array:
0 1 2 3 4
Size of pointer: 8
Elements in array:
0 1 2 3 4
Size of array: 20
Elements in array:
0 1 2 3 4
Size of array: 20
Elements in array:
0 1 2 3 4

Conclusions

In C, there is no passing by reference. In C++, passing by reference is allowed. Although there is nothing right or wrong, probably the best practice in C++ is to design the function interface that takes a reference to array. It is not a big deal if originally the function interface takes a decayed pointer, we could switch the function interface later without having to change the implementations that calls the function.

Author

Lei Mao

Posted on

07-25-2022

Updated on

07-25-2022

Licensed under


Comments