C++ native array handling


C++ and modern C are two different languages. C++ branched from K&R C.  Today it is not based on modern C99, C11 and beyond.

There are C++ users, who truly believe native arrays are to be avoided in modern C++. I disagree. Another side of that coin is perhaps, somewhat like me, you might also believe no feature of modern c++ std lib, has to be used just because it exists, without knowing why.

Thus the scene is set. Two opposing groups of C++ practitioners. But they both agree: std::array is a “good thing”. They disagree when, how and why to use it.

What I think? When you think you need it, you better be sure it is the right tool to solve your problem and you be sure you use it properly too.  Enter: native array. Why does it exist C++ zealots might be bold to ask?  Let’s try and reason with them. For starters:



The native array is a foundation cornerstone

Of course, the native array is one of the cornerstones in the foundations of modern C and modern C++. Here is the diagram you have seen before, perhaps many times. But please stop and understand, this is first and foremost the diagram representing the C++ key concepts. This is not K&R C diagram.

Contiguous memory and iterators (begin & end), operating on it.

Iterators are conceptually the pointer to the first element of the contiguous memory (aka array) and a pointer to the  “one behind” (aka “+1”) address after the last element. Repeat: The end is last + 1.

And, next is yet another important detail many of you dear fellow C++ developers surely knew a long time ago. But the one not perhaps that much in your focus recently.

Key abstraction

T (&)[N]

That is a declaration of the reference to the array of size N,  I like to call it: NAtive aRray reFerence or abbreviated NARF.  In standard C, this does not exist. Closest to it we  have in  C is

T (*) [N]

Pointer to an array. A source of many confusions. Because C++ array decay does not transform array to that. Array decay is not transformation to the pointer to the array. Array decay is simply pointer to the first arrsay element.

Why is NARF “key abstraction”?

Most importantly, NARF is how C++ passes arrays into the functions and out of the functions.  Basically NARF is always used and pointer to array is never used.

One good reason NARF is important, is that native array reference, is  the type compiler uses for native arrays  used in the modern c++ range-based for loop,

#include <iostream>
#include <cstdlib>

int main()
//native array of 
//pointers to char
const char * nativarr[]
{"Look", "native", "array", "iteration"};
    // this transforms nativarr into NARF!
    // const char (&nativarr)[4]
    for ( auto word : nativarr ) {
          std::cout << word << " ";

This works because C++ compiler sees T(&)N  aka  (const char * (&)[4], above) and thus, in turn, knows the number N, of elements in the nativarr above.  In turn, a compiler can also deduce begin and end iterators to the NARF.

// for the abovecompiler 'knows'
auto  begin = nativarr ;
// one after the end
auto    end = nativarr + 4;

Hint: Remember the diagram on the top?

C and C++ native arrays can be passed in as value arguments

// C99
void take_array ( int size, char string[ size ] ) ;

That is legal C99. That is not C++. Also, we can not return arrays from functions.

C++ NARF can be passed as the return type

In C++ there is one a bit different way to return native arrays from functions. One can declare arguments as array references, and return them from functions, etc.

using narf_char_256 = char(&)[0xFF];
// take native array reference
// aka NARF
// and return the
// same type too
narf_char_256 all_zeros(narf_char_256 narf ) 
  memset(narf, 0, 0xFF);
  return narf;
  // be carefull not to return reference to local!

In standard C++ it is illegal to declare NARF (native array reference) to the array of unknown size. Hence the repeated usage of ‘256’ above.

// no can do
// this declaration is not legal
// in standard C++ 
// size must be given
using illegal_NARF = char(&)[]

Above is illegal and will not compile. Native array reference, size must be known at compile time. NARF and template are marriage made in heaven.

// common C++ idiom for array passing
// is using NARF
template< typename T, size_t N> 
constexpr size_t array_count ( const T(&) [N] )
    return N;

Thsat is the standard way. It works for any type T and any size N. It is generic. Amd obvious candidate for compile time.

It seems you are by now beginning to like native arrays in C++.

We have to talk about std::array

I know, some of you are itching on this one by now.  You want to shout out: Why not just use the std:array? !

The answer is simple: The integration.

std::array is a full-blown C++ std container. Very basic but still a container using one native array inside. NARF is not a container. NARF is a tiny abstraction representing a reference to the native array. NARF could be visualized as an atomic building block of larger abstractions.

And yes, there is no std::array in the land of Modern C.

Modern C

C++ is/was based on K&R C. Modern C too. C++ is not based on modern C.  This common heritage requires a lot of native arrays on both sides of the proverbial fence, between C run-time (CRT) and C++ above it. Just look into the MSVC STL source peppered with C files.  In there you will find a *lot* of very important C implementations operating on native arrays, aka contiguous memory. Very often using two native pointers representing array begin and end concepts, straight from the C++ space one level above. (Hint: lookup that diagram on top now)

To dance in and out of these C functions would be very complex dance without native arrays being possible in the modern C++ code.  C/C++ string literals are perhaps the prime example of that “dance”.

String comparisons are (mega) important, functions written in C.  Their declarations are very similar to these two as an example:

// this is C code, by one "DBJ"
int __cdecl dbj_ordinal_compareA(
	const char *_string1, const char *_end1,
	const char *_string2, const char *_end2);

int __cdecl dbj_ordinal_compareW(
	const wchar_t *_string1, const wchar_t *_end1,
	const wchar_t *_string2, const wchar_t *_end2);

Now imagine using these from modern C++ but without native arrays,  and also without references to native arrays, at hand. I am sure you understand that might work but will be far inferior implementation to the fortunate reality of using native arrays in modern C++.