I would like to pass small arrays of variable size as parameters in modern C++, ie:
func({1,2,3,4});
And it should be as fast as possible, ideally without heap allocation.
Things I've tried:
C style array
void func(int * arr, int arrayCount) {
for (int i = 0; i < arrayCount; i++) {
//use arr[i];
}
}
int arr[] = {1,2,3,4};
func(arr, 4);
This is fast and efficient, but adds an extra arrayCount variable which is prone to user error, and the function call is now broken into two lines.
std vector
void func(vector<int> arr) {
for (int &a : arr) {
//use a
}
}
func({1,2,3});
This is very clean but it's remarkably slow due to copying data over to the heap. Changing the signature to use a universal ref:
void func(vector<int> && arr) {
Doesn't seem to make any difference.
std initializer_list
void func(initializer_list<int> arr) {
for (int &a : arr) {
//use a
}
}
func({1,2,3});
This is a 50x speed improvement over the vector (approximately)! Using a && again makes no difference, but it is still (approximately) 5-10x slower than the C style syntax due to the overhead of initializer_list creation and iteration. Still acceptable for many use cases, but the overhead seems a little unnecessary.
Thinking maybe compilers had gotten smarter, I tried the following:
std array
template <int arrayCount>
void func(array<int, arrayCount> arr) {
for (int &a : arr) {
//use a
}
}
The compiler can not infer the size from the call, so it is necessary to write
func<3>({1,2,3});
And in any case, this is no faster than the initializer list.
Variadic templates are out of the question for a number of reasons, because they don't support multiple array parameters and the syntax is difficult to navigate.
Is there a way to get both clean syntax and fast performance? It seems like it would be easy to add some syntactic sugar to wrap the C style array notation.
Clarification:
These tests were performed in MSVC 2017 with default optimization (O2):
timer.start();
for(int i = 0; i < 100000; i++) {
func({i, i+2, i+3, i+4});
}
timer.stop();
or for the c style:
for (int i = 0; i < 100000; i++) {
int arr[] = {i, i+2, i+3, i+4};
func(arr, 4);
}
With the passed in values being added to a static variable.
Review:
See Robert's answer below for the syntax for passing arrays using generics that avoids STL and has similar performance to the C style option, but is limited in its ability to handle empty arrays.
My take away is that the initalizer_list notation is probably fast enough in most use cases, the vector notation should be avoided, and when performance is absolutely critical, consider the generic array approach or C style arrays depending on the use case. MSVC doesn't optimize these as well as GCC/Clang.