Getting reference to the raw array from std::array
What's the canonical way to get an std::array's underlying raw (C) array?
There is no way of getting the underlying C array.
Also, is there a good reason why data() returns a raw pointer, and not a reference to the underlying raw array, or is this just an oversight?
It's backwards: there is no good reason for the std::array
to provide the underlying C array. As you already said, the C array would be useful (over the raw pointer) only with functions getting a reference to C arrays.
When was the last time you had a function:
void foo(int (&arr)[5])
Me? Never. I never saw a function with a C array reference parameter with the exception of getting the size of array (and rejecting pointers):
template <class T, std::size_t N>
auto safe_array_size(T (&)[N]) { return N; }
Let's dive a little into why parameters references to arrays are not used.
For starters, from the C area pointer with a separate size parameter was the only way to pass arrays around, due to array-to-pointer decay and lack of reference type.
In C++ there are alternatives to C arrays, like std::vector
and std::array
. But even when you have a (legacy) C array you have 2 situations:
- if you pass it to a C function you don't have the option of reference, so you are stuck to pointer + size
- when you want to pass it to a C++ function the idiomatic C++ way is to pass begin + end pointers.
First of all a begin + end iterators is generic, it accepts any kind of containers. But is not uncommon to see reference to std::vector
when you want to avoid templates, so why not reference to C array if you have one? Because of a big drawback: you have to know the size of the array:
void foo(int (&arr)[5])
which is extremely limiting.
To get around this you need to make it a template:
template <std::size N>
void foo(int (&arr)[N])
which beats the purpose of avoiding templates, so you better go with begin + end template iterators instead.
In some cases (e.g. math calculations on just 2 or 3 values which have the same semantics, so they shouldn't be separate parameters) a specific array size is called for, and making the function generic wouldn't make sense. In those cases, specifying the size of the array guarantees safety since it only allows passing in an array of the correct size at compile-time; therefore it's advantageous and isn't a "big drawback"
One of the beauties of (C and) C++ is the enormous scope of applicability. So yes, you will always find some fields that use or need a certain unique feature in an unique way. That being said, even in your example I would still shy away from arrays. When you have a fixed number of values that shouldn't be semantically separated I think a structure would be the correct choice over arrays most of the time (e.g. glm::mat4
instead of float[4]
).
But let's not forget what std::array
is: a modern replacement for C arrays. One thing I learned when analyzing options is that there is no absolute "better than". There is always a "depends". But not in this case: std::array
should unquestionably replace C arrays in interfaces. So in the rare case where a fixed size container is needed as a reference parameter it doesn't make sense to enable encouraging the use of C arrays when you already have an std::array
. So the only valid case where exposing the underlying C array of std::array
is need is for some old libraries that have C array reference parameters. But I think that in the bigger picture adding this to the interface it is not justified. New code should use a struct (btw std::tuple
is getting easier and easier to use by each standard) or std::array
.
AFAIK, There's no direct or typesafe way to do it, but one work around if you need to pass to a function (with a signature you cannot change to std::array
) is by usingreinterpret_cast
like this:
some_function(*reinterpret_cast<int (*)[myarr.size()]>(myarr.data())));
If you wanted to make it safer:
#include <array>
void passarray(int (&myarr)[5]){}
template <typename ValueT, std::size_t size>
using CArray = ValueT[size];
template <typename ValueT, std::size_t size>
CArray<ValueT, size> & c_array_cast(std::array<ValueT, size> & arg) {
{
return *reinterpret_cast<CArray<ValueT,size>*>(arg.data());
}
int main()
{
std::array<int,5> myarr = { {1,2,3,4,5} };
passarray(*reinterpret_cast<int (*)[myarr.size()]>(myarr.data()));
passarray(c_array_cast(myarr));
return 0;
}