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.
// C style interface template <typename T> voidprint_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> voidprint_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> voidprint_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> voidprint_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; }
$ 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.