C++ std::array is “funny”

The other day on stack overflow one fellow C++ developer remarked : “std::array is funny”. And it indeed is funny.

Ancient C manuscript restored
Ancient C manuscript restored

So, what’s the issue?

The issue is a native array. Very often we need it. and we know it is inside some std container or type but we can not get to it. Easily.

C++ has this curious thing called reference. And an even curiouser thing called array reference.  How is this helping us?  For starters, this is how we declare, define and use those in standard C++ (step by step):

Thus we know how to declare, make and use the reference to a standard C++ native array of chars, in an easy and compliant way. Fine. so what?

Given std::array of chars how do we get to it’s internal native array, and use it as a such?

To transform the result of std::array data()  method to the reference to its internal native array one needs to dive deep into the toxic waste of C style casts.

We need to cast the result of data from char pointer to the pointer of the internal array of 3 chars. And then we have to dereference what we got so that we can assign it to the reference of the internal array. And yes, we need to know the size of the std::array we are using. Ugly as hell this is.

Side note: I deliberately do not use reinterpret_cast as I do not see it safe or helpful in any use case. C style cast is at least much more obvious warning some stunt is going on.

An legacy example

I have written dbj::strnlen and dbj::strlen , which are nicely optimized for inbuilt (aka “C style” aka “native”) arrays. Here are the declarations.

Now. A real-life example.

std::array size() returns simply the length of its internal native array. std::array does not care what is the T(&)[N]. If the user is having std::arrayinstance   acting as a char array, this user can not use above optimizations since std::array does not have a method for “revealing” the inbuilt array as a native array. The same native array one it is using for its implementation. It just has a method data(), which returns the constant pointer to T, aka type of elements being stored in the array. Therefore:

Above, charbuff.data() returns const char * , and is thus not  “caught” by the compiler and it is not compiled with highly efficient template<size_t N> dbj::strnlen( char (&arr_)[N] ), which executes in “zero time”.

charbuff is thus “hiding” char array[256], which we cannot reach as a native array, but instead as a const pointer to it.

When passed to functions, native arrays in C++ automatically “decay” in pointers to arrays.  And in everyday life, we do need native arrays. A lot.

In C/C++ there is no faster collection of data items as an intrinsic data structure.

We need to deal with C API’s.  This is why among other things pointers are conceptualized as iterators, so they can operate on inbuilt arrays as on any other C++ container, in a transparent manner.

But. We digress. Back to the task at hand.

Here is the design aka the plan. Surely we can do something.

Inheritance for implementation is evil. Instead of developing specialized: std:array derivative, with a method e.g.internal_array_reference(),  we will use what we got from modern C++ and encapsulate the above nightmare into a palatable solution.

Version One

Function template, with scary looking template arguments.  Do not despair if you have not seen this before, thinking “Oh, this C++ is endless,  I will never learn it all”. Them template arguments are just a convenient place (I happen to like) to write a single function template, with internal typedefs we need.

Note. Be sure to receive the result like this:

Otherwise, if we would not do it that way asauto & we would be left with the pointer to the internal array, again. By the way, above we have produced one very nice dangling reference, by creating std array as an argument only, and then returning the reference to its internals. We shall deal with this obvious issue right now. We will explicitly delete the function signature that allows for references to temporaries.

Beware of using auto though. Here is how we would also make a mistake and be left with what we do not want. A pointer.

Above solution has its deficiencies but it works, if you are  (very) careful.  But let us travel to a better solution without further ado.

Version Two

Usage is classical standard C++. We instantiate the template into the type we will use to deal with exact array type we need to deal with.

Thus, we have fully encapsulated the solution in one templatestruct  This handler struct is keeping no data. Just std::array type handled and types missing from std::array as it stands today in standard C++ .  And there are two static methods to return reference and a pointer to the internal native array of the std::array argument.

The  solution to the std::array issue in this post is this:

Enjoyed so far? Well, not yet.

Caveat Emptor

Caveat emptor is Latin for “Let the buyer beware”. So far the methods in the code above, are receiving const references to  std::array and then manipulate the data() result, with casting stunts,  to return what we want. And yes we have explicitly banned using temporaries as arguments.

Still,  if and when the original array goes out of scope the result of these two functions becomes invalid.  Either a dangling reference or a dangling pointer, that is.

So please treat them results the same as you are treating any other references or pointers.  What this means?

This means, one should remember, standard C++ is best when dealing primarily with values, not references or pointers. That simply means:

Do not carry around references or pointers, but values.

This is a classical performance vs resilience balance, one has to perpetually and skillfully solve while using powerful programming languages like standard C++ is.


Simple Language
Simple Language for simple solutions