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

An 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. 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 optimized  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.

And in C/C++ , one is not allowed to just return the array by value from e.g. function or assign array to array as both are values, etc. When passed to functions, native arrays in C++ automatically “decay” in pointers to arrays. “Legendary” std::vector was invented so that we “simply forget” about “C array intricacies”, which might be ok, unless we need good old simple classical array. And we do need native arrays. A lot.

In C++ there is no faster collection of data items as an intrinsic data structure. We need native arrays very much so. 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.

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++:

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. 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.

Here is the design aka the plan

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 as this:

Otherwise, if we would not do it that way asauto & we would be left with the pointer to the internal array, again.

This indeed works but let us travel to a better solution without a further ado.

Version Two

Usage is classical standard C++
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:

Enjoy …? Not yet.

Caveat Emptor

Caveat emptor is Latin for “Let the buyer beware”. The two methods in here are receiving const references to  std::array and then manipulate the data() result, with casting stunts,  to return what we want.

So 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.

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

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.