Can Std::Begin Work with Array Parameters and If So, How

Can std::begin work with array parameters and if so, how?

Yes, std::begin and std::end can work with parameters that are C style arrays.

The trick is in passing a parameter that's a C style array. When you specify a 1D array as a normal parameter to a normal function, its type is silently adjusted from "array of T" to "pointer to T". When you call that function, what gets passed isn't the array (as an array), but a pointer to the first element of the array.

It is, however, possible to pass an array by reference to a function template:

template <class T, size_t N>
void function(T (&array)[N]) {
// function body here
}

In this case, where you're passing an actual array (albeit, by reference) rather than a pointer, you can use std::begin and std::end perfectly well. For example:

template <class T, size_t N>
T sum(T (&array)[N]) {
return std::accumulate(std::begin(array), std::end(array), T());
}

Now passing an array is trivial, such as:

int array[] = {1, 2, 3, 4};

auto total = sum(array);

std::begin and std::end themselves are (or at least can be) implemented similarly to sum--the array is passed by reference, so they can look something like this:

template <class T, size_t N>
T *begin(T (&array)[N]) {
return array;
}

template <class T, size_t N>
T *end(T (&array)[N]) {
return array + N;
}

Note that although these were added to the standard more recently, they don't require any particularly tricky use of templates, so the implementation above should work fine with a plain old C++98 compiler (and, if memory serves, even with pre-standard compilers such as VC++ 6).

Why do std::begin() and std::end() work with fixed arrays, but not with dynamic arrays?

std::(c)begin() and std:(c)end() are explicitly overloaded to work with fixed-sized array types. Information about the size of the arrays is not lost, as the sizes are part of the array types themselves. An implementation of these overloads may look something like this:

template<typename T, size_t N>
T* begin(T (&arr)[N])
{
return arr;
}

template<typename T, size_t N>
const T* cbegin(const T (&arr)[N])
{
return arr;
}

template<typename T, size_t N>
T* end(T (&arr)[N])
{
return arr + N;
}

template<typename T, size_t N>
const T* cend(const T (&arr)[N])
{
return arr + N;
}

Thus, std::begin(arr2) and std::end(arr2) are perfectly valid only when arr2 is an int[N] fixed-size array type. The compiler can deduce values for the T and N template parameters based on the type of fixed array being passed in, eg:

int arr2[5] = { 1,2,3,4,5 };

if (find(
begin(arr2), // deduces T=int, N=5, thus calls std::begin<int,5>(arr2)
end(arr2), // deduces T=int, N=5, thus calls std::end<int,5>(arr2)
5)
!= end(arr2) // deduces T=int, N=5, thus calls std::end<int,5>(arr2)
)
{
cout << "found";
}

Conversely, there is simply no way to provide similar 1-parameter overloads for an int* pointer, as there is no information about the size of the array being pointed at (or even whether an array is even being pointed at) for std::end() to return a valid iterator to the end of the array:

template<typename T>
T* begin(T *arr)
{
return arr; // OK
}

template<typename T>
const T* cbegin(const T *arr)
{
return arr; // OK
}

template<typename T>
T* end(T *arr)
{
return arr + N; // NOT OK, WHAT IS N SUPPOSED TO BE?!?
}

template<typename T>
const T* cend(const T *arr)
{
return arr + N; // NOT OK, WHAT IS N SUPPOSED TO BE?!?
}

However, you can provide your own 2-parameter overloads 1, so you can explicitly pass in the dynamic array size for N, eg:

namespace std
{
template<typename T>
T* begin(T *arr, size_t N)
{
return arr;
}

template<typename T>
const T* cbegin(const T *arr, size_t N)
{
return arr;
}

template<typename T>
T* end(T *arr, size_t N)
{
return arr + N;
}

template<typename T>
const T* cend(const T *arr, size_t N)
{
return arr + N;
}
}
int *arr = new int[5]{ 1,2,3,4,5 };

if (find(
begin(arr,5), // deduces T=int, explicit N=5, thus calls std::begin<int>(arr2,5)
end(arr2,5), // deduces T=int, explicit N=5, thus calls std::end<int>(arr2,5)
5)
!= end(arr2,5) // deduces T=int, explicit N=5, thus calls std::end<int>(arr2,5)
)
{
cout << "found";
}

Live Demo

1: you ARE NOT allowed to add new functions to the std namespace, but you ARE allowed to add custom overloads for existing standard functions. std::swap() is an common example of that, though utilizing ADL is generally a better choice than extending the std namespace.

C++; Pass std::array as a Function Parameter

Your function definition still needs template parameters since the compiler has no way of knowing what SIZE is

template<size_t SIZE>
// ^^^^^^^^^^^^^^^^^^
void QuickSort(array<unsigned, SIZE>& arrayName)
{
/// whatever the function does.
}

Why does std::begin behave differently when called in a nested function

An array can't be passed by value, so your array decays into a pointer when passed to checkNested(), and std::begin() is not defined for a pointer, hence the error.

void checkNested(int val [10]) is just syntax sugar for void checkNested(int *val).

If you pass the array by reference instead, then the code will work:

void checkNested(int (&val) [10])

begin and end function for built-in array types

The problem that the compiler faces, is that it has no way to guess the size of argv, because it is not an array but a mere pointer.

Arrays can always be used as pointers, because an array can decay to a pointer to its first element. But the inverse is false! You can access the elements of an array through the array or through a pointer exactly the same, but the pointer and the array are not the same:

int a[5];
int *p = a; // p[i] and a[i] are now the same
size_t s = sizeof(a); // s is 5 * sizeof(int) 20 on common (32 bits) architectures
size_t s2 = sizeof(p); // s2 is just sizeof(int *) 4 on 32 bits architecture

The convention says that argv has argc elements (and that argv[argc] is a null pointer). But std::begin and std::end do not make use of that convention. So do it by hand as a nice programmer:

#include <iostream>
#include <iterator>

int main(int argc,char *argv[])
{
char **first=argv,**last=argv + argc;
}

C++ Function that accepts array.begin() and array.end() as arguments

As far as I understand, the begin/end functions return a pointer to the first/last element of the array

No. begin and end return iterators. The standard library works with iterators. Iterators are a generalization of pointers.

Iterators behave like pointers and you use them like pointers (e.g. *it to access the element), with some caveats: not all iterators have all the operations a pointer does. A pointer satisfies the random access iterator concept, so on some implementations the iterator of std::array could be just an alias for the pointer type, but you can't rely on that. E.g. on the same compiler it can be a pointer for the release build, but a full class for the debug build.

The idiomatic way is to write:

template<class It>
void foo(It begin, It end) {
for (auto it = begin; it != end; ++it) {
const auto& elem = *it;
// ..
}
}

Since C++20 we should transition from iterator pairs to ranges:

void foo(std::ranges::range const auto& r) {
for (const auto& elem : r) {
// ...
}
}

How does the C++ begin and end work when argument is an array?

So to see how std::end works we can look at How does std::end know the end of an array? and see that the signature for std::end is:

template< class T, std::size_t N >
T* end( T (&array)[N] );

and it is using template non-type parameter to deduce the size of the array and it is just a matter of pointer arithmetic to obtain the end:

return array + N ;

For std::begin the signature is identical, with the exception of the name:

template< class T, std::size_t N >
T* begin( T (&array)[N] );

and calculating the beginning of the array is simply a matter of array to pointer decay which gives us a pointer to the first element of the array.

In C++14 these both become constexpr.

Passing a std::array of unknown size to a function

Is there a simple way to make this work, as one would with plain C-style arrays?

No. You really cannot do that unless you make your function a function template (or use another sort of container, like an std::vector, as suggested in the comments to the question):

template<std::size_t SIZE>
void mulArray(std::array<int, SIZE>& arr, const int multiplier) {
for(auto& e : arr) {
e *= multiplier;
}
}

Here is a live example.



Related Topics



Leave a reply



Submit