C++ How to create unique types, from the all stack template. Simple matrix

Consider this:

If we create few template definitions we get the same number of distinctive types, like so:

So far so good. But. count is shared. It is the same for everyone. All concrete types defined from the same template and all the instances of those types.

Since the count is shared on the type level (aka static user-defined type, member) one change applies to every other type.

This is not so good unless it is a design requirement.

The context

The other day I have developed a handy little template to hold and provide, a plain 2d array on the stack (aka the “matrix”). It is also fully “compile time capable” with the full static internal presentation.  Very handy to enforce (gently) company-wide policies of static matrices in standard C++, and to give in the same time some useful functionality in return. I am calling this template “almost a pod”.

Why this template?

In standard C++ community 2d array’s are the source of endless debate. Especially the large ones created on the heap. But. 90% of use cases require, a plain old 2d array :

// a pod example
int matrix[3][3];

As visible in the code above standard C++ is giving us quite a few mechanisms and features to encapsulate the rules of using such matrices. Usage samples:

Not a single instance in sight. All is in the type. Because implementation is keeping it all as one static simple 2d array class member.

Are we done here, then?

No. We are not done here yet.  Since everything is static inside this type  (aka template definition) it is shared, between all instances of the same type. Actually, there are no instances required. We can make  and use them instances, just to show the issue:

We are getting 3 identical matrix outputs. This is because there is no instance bound data inside, it is all shared between all instance of the type mx9 There is a single data member inside and it is static,  so-called “class-wide”

// just a pod 2D array on the stack
inline static value_type data_[R][C]{};

Thus each instance of the template definition for the same template arguments T,R,Ccombination will share the same 2d array hidden inside.

In this context, there are no objects (aka instances) just classes, which are in this case template definitions.

How do we differentiate between them?

We have a problem.  What are we going to do? We need, for example 3×3 matrices of int’s but we have no instances to keep them separate in the memory.

We could redesign and re-do this template so that nothing is static, true. But this will lead to completely different run-time semantic. And we want, compile time. We want our matrices as fast as possible, as simple as possible, and not made on the (slower) heap.  Here is my solution.

Artificially different template definitions

Add template argument, and use it to make them definitions different.  Template arguments, on our matrix, are now these:

There is a new template argument:UID_  Let us assume UID_ is just an ever increasing number.  When we create template definitions (aka types), simplified view on what we have now is this:

The last template argument effectively creates two distinct types above, which are actually both the same ‘thing’: A static matrix, of size 3×3 with integers in the cells. On the stack, all static. Simple, fast and nothing on the heap.

Here is our all stack all static matrix. Just the changes:

The UID_ argument added.  This is what makes it possible.

UID_ has to be a compile time constant. My quick (but not dirty) implementation:

#define DBJ_UID __COUNTER__ +  dbj::util::inner::hash(__FILE__)

Where __COUNTER__ is MSVC predefined macro value giving 1,2,3 … on the level of the compilation unit, and the hash function is my constexpr implementation of the famous  djb2  hash algorithm.

Actual usage is a bit unusual because instances are not necessary. Just the types. Example

To use the test function declared as above, we pass our matrix as a type and the rest as value:

Above means there is no standard C++, copy or move mechanisms being used whatsoever, The matrix stays in one place, where it was first created.

If one needs, to pass instances of this matrix in and out of function, as values, this is perfectly unnecessary but perfectly doable:

And the usage:

Add that to the last usage example, and you will understand.

We receive argument as value,   we return the matrix as value. It does not matter. There is no instance bound data inside, Compiler has no job to do. Copy/move elision is not applicable. There is nothing to copy or move. No instances necessary 🙂

As ever if anybody needs advice on how to use dbj matrix,  in her project please do let us know, in the comments below, or through any other modern communication channel available.

Leave a Reply

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