Passing in an array of structs to a function

Hi,

I'm looking to pass an array of structs to a function and wanted to check that the way I'm doing it and using internally within the function is good practice and valid.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
struct s_parameters_t
{
    const uint32_t value1;
    float32_t value2;
};
 
const s_parameters_t test_parameters[2] = {{1,2.0f},{2,3.0f}};
 
float32_t sum(const s_parameters_t* param, int32_t length)
{
    float32_t total = {0.0f};
 
    for (int32_t i = 0; i < length; i++)
    {
        total += param[i].value2;
    }
 
    return total;
}
 
sum(test_parameters, 2);

Last edited on
Why an array and not a vector?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <vector>
#include <iostream>

using float32_t = float;

struct s_parameters_t {
	const uint32_t value1 {};
	float32_t value2 {};
};

float32_t sum(const std::vector<s_parameters_t>& param) {
	float32_t total{};

	for (const auto& [v1, v2] : param)
		total += v2;

	return total;
}

int main() {
	const std::vector<s_parameters_t> test_parameters { {1, 2.0f}, {2, 3.0f} };

	std::cout << sum(test_parameters) << '\n';
}


Or if you really need a c-style array, then pass by template ref so the number of elements is needed to be passed as a param:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

using float32_t = float;

struct s_parameters_t {
	const uint32_t value1 {};
	float32_t value2 {};
};

template <size_t N>
float32_t sum(const s_parameters_t (&param)[N]) {
	float32_t total{};

	for (const auto& [v1, v2] : param)
		total += v2;

	return total;
}

int main() {
	const s_parameters_t test_parameters[]{{1, 2.0f}, {2, 3.0f}};

	std::cout << sum(test_parameters) << '\n';
}

Last edited on
I don't think passing c-style arrays by const reference is necessarily an improvement. It makes it impossible to call the function if all you've got is a pointer to the first element and a length, or if you are not using all elements in the array (maybe you only want to pass a sub-portion of the array). Making the length of the array a template argument also leads to a lot of code duplication (assuming you pass arrays of many different lengths) which might not be optimal from a code size perspective.

C++20 introduces std::span which is essentially a wrapper around a pointer and a length. This makes passing arrays and other contigous containers (e.g. std::vector) easier and less error prone.

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
#include <iostream>
#include <cstdint>
#include <vector>
#include <span>

using std::uint32_t;
using float32_t = float;

struct s_parameters_t {
	const uint32_t value1 {};
	float32_t value2 {};
};

float32_t sum(std::span<const s_parameters_t> param) {
	float32_t total{};

	for (const auto& [v1, v2] : param)
		total += v2;

	return total;
}

int main() {

	const std::vector<s_parameters_t> test_parameters_vec { {1, 2.0f}, {2, 3.0f} };
	std::cout << sum(test_parameters_vec) << '\n';

	const s_parameters_t test_parameters_arr[] { {1, 2.0f}, {2, 3.0f} };
	std::cout << sum(test_parameters_arr) << '\n';
}
5
5


If you're not using C++20 yet you could of course implement your own span type, or use a library such as Boost, but it might not be worth the trouble. Your original code uses a tried-and-true approach. It might look feel a bit more like "C" but there is nothing necessarily wrong in using it in C++.

The traditional C++ approach has otherwise been to write such "functions" using iterators. This has the added benefit that you can even use it on non-contiguous containers such as std::deque or std::list.

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
#include <iostream>
#include <cstdint>
#include <vector>
#include <list>

using std::uint32_t;
using float32_t = float;

struct s_parameters_t {
	const uint32_t value1 {};
	float32_t value2 {};
};

template <typename Iter>
float32_t sum(Iter begin, Iter end) {
	float32_t total{};

	for (auto it = begin; it != end; ++it)
		total += it->value2;

	return total;
}

int main() {
	
	const std::vector<s_parameters_t> test_parameters_vec { {1, 2.0f}, {2, 3.0f} };
	std::cout << sum(std::begin(test_parameters_vec), std::end(test_parameters_vec)) << '\n';
	
	const s_parameters_t test_parameters_arr[] { {1, 2.0f}, {2, 3.0f} };
	std::cout << sum(std::begin(test_parameters_arr), std::end(test_parameters_arr)) << '\n';
	
	const std::list<s_parameters_t> test_parameters_list { {1, 2.0f}, {2, 3.0f} };
	std::cout << sum(std::begin(test_parameters_list), std::end(test_parameters_list)) << '\n';
}
5
5
5
Last edited on
Hi both,

Thanks for your thoughts! Interesting read. I've only got a basic level of c coding knowledge which is why I thought I'd pass a reference to an array, but I'll look into the more modern ways of doing this as you've suggested.

I was wondering though (out of interest) is it still valid to access elements of the array using the . notation i.e.param[i].value2 even though I've passed in the array using a pointer? I wanted to check i'm not using the syntax improperly - even though as you've suggested there are more modern ways of doing this.
Last edited on
Providing the index is within range (ie 0 to length - 1), then it's fine.

For this purpose, pointer and array are interchangeable - indeed passing a c-style arrays 'degrades' to a pointer.
If, however, in the OP param comes from passing a pointer from say new or std::unique_ptr, then you do need to pass the number of elements as this cannot be established and in this case std::s[an won't wotk as it cannot determine begin/end of the span as is required.
Last edited on
The C syntax rules (which was later inherited by C++) was intentionally designed to be able to use arrays and pointers to the first element in arrays the same way.

1
2
3
4
int arr[5]; // array of 5 ints
int* p = arr;// pointer to first element in arr. Equivalent to `int* p = &arr[0];`
arr[0] = 1; // set the first array element to 1.
p[1] = 2; // set the second array element to 2. 

When declaring a function that takes an array as argument (without using references) you're actually declaring a function that takes a pointer as argument.

The three following function declarations means exactly the same (i.e. a function named foo that takes an int* as argument).

1
2
3
void foo(int arr[5]);
void foo(int arr[]);
void foo(int* arr);

This has sometimes lead beginners to think that arrays are just (const) pointers. Some teachers even tell them so. But it's not true and eventually you will run into situations where you need to know the difference. A very common mistake beginners make is when they try to use sizeof to figure out the length of an array without realizing they have a pointer and not an array so the calculated length becomes incorrect. A safer alternative is to use std::size, which also cannot calculate the array length from pointers, but if you try you'll get a compilation error which is much better than an incorrect length at runtime.
Last edited on
With C++, if you ask 6 programmers, you'll get at least 9 different! :) :)
> If, however, in the OP param comes from passing a pointer from say new or std::unique_ptr,
> in this case std::span won't wotk

std::span with a dynamic extent (this is the default) would work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <memory>
#include <span>

int main()
{
    std::size_t n = 1234 ;
    // std::cin >> n ;
    auto ptr = std::make_unique< int[] >(n) ;
    
    // create a span with std::dynamic_extent 
    std::span span( ptr.get(), n ) ; 
    std::cout << span.size() << '*' << sizeof(int) << " == " << span.size_bytes() << '\n' ;
}

http://coliru.stacked-crooked.com/a/8eb5026453acfe29
Topic archived. No new replies allowed.