Are we done here, then?
No. We are not done here yet. I would like to turn your attention to the problem hidden inside and its solution in dbj::static_matrix
.
A general problem description.
If everything is static inside the type it is all shared, between all instances of the same type. Consider this:
1 2 3 4 5 6 7 |
// this is not a type // this is a template template<typename C> struct buffer { // every instance of template // will share this value static inline mutable size_t count = 1024U ; }; |
If we create a few template definitions we get the same number of unique types, like so:
1 2 3 4 |
// 3 concrete unique types using c_buffer = buffer<char>; using w_buffer = buffer<wchar>; using n_buffer = buffer<int> ; |
So far so good. But. count
is shared for all instances of the same type.
1 2 3 4 5 6 7 |
// two instance of the same type c_buffer instance_1 ; c_buffer instance_2 ; // instance_1.count = 42 ; // true bool the_same_value = instance_2.count == instance_1.count ; |
Since the count is shared on the type level (it is a static type member) one change applies to every other instance of that type. That is not so good unless it is a design requirement.
Same problem in the context of dbj::static_matrix
Let us assume dbj::static_matrix
has this problem. Let us assume that is the issue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// the unique type // but made from the wrong stack matrix using mx9 = stack_matrix<int, R, C>; // two instance of the same type // made on the heap mx9 * mt1 = new mx9; mx9 * mt2 = new mx9; // get the reference to cell [1][1] from the type // because we can mx9::matrix_ref_type mx = mx9::data(); // give it value 1 mx[1][1] = 1; // print all three of them mx9::printarr( dbj::console::print ); mt1->printarr( dbj::console::print ); mt2->printarr( dbj::console::print ); // they are all the same ... assert(mx[1][1] == mt1->data()[1][1] == mt2->()[1][1]) // |
We are getting 3 identical matrix outputs. This is because there is no instance-bound data inside, it is all shared between all instances of the unique type mx9
There is a single data member inside and it is static, so-called “class-wide”
1 2 |
// just a pod 2D array on the stack inline static value_type data_[R][C]{}; |
Thus each instance of the template definition (aka the type) for the same template arguments T,R,Cthe
combination will share the same 2d array hidden inside.
What matters here are no objects (aka instances) but types aka classes. Which are in this case template instantiations.
Differentiate between the same types
What are we going to do? We need, for example, two 3×3 matrices of int but we could not rely on instances to keep them separate in the memory.
We could redesign and redo this template so that nothing is static, or true. But this will lead to completely different run-time semantics. 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 the solution.
Make different template definitions
Add type Id as the template argument, and use it to make the definitions different.
Thus, template arguments, on our matrix, will be these:
1 2 3 4 5 6 7 |
template < typename T, size_t R, size_t C, unsigned long UID_ > class stack_matrix final ; |
There is a new template argument:UID_
It is quite enough for the UID_ to be just an ever-increasing number. When we create template definitions (aka types), the simplified view of what we have now is this:
1 2 3 4 5 6 |
// two differnt types, thanks // to the UID template argument using mx9a = stack_matrix<int, 3, 3, 1 >; using mx9b = stack_matrix<int, 3, 3, 2 >; |
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. But two different types.
The implementation
The UID_
parameter of that template. This is what makes it possible. And, UID_
has to be a compile-time constant. My quick (but not dirty) implementation:
1 |
#define DBJ_UID __COUNTER__ + 100 |
Where __COUNTER__
is MSVC/GCC/CLANG predefined macro value giving 101,102,103
… on the level of the compilation unit.
1 2 3 4 5 6 7 8 9 10 |
// dbj stack static matrix solution // two idfferent types // separated by the UID_ value argument using mx9a = stack_matrix<int, R, C, DBJ_UID >; using mx9b = stack_matrix<int, R, C, DBJ_UID >; // different static_assert(mx9a::uuid() != mx9b::uuid()); |
Application-wide solution
I assume we all know what the “compilation unit” is in C/C++. Sometimes called “translation unit”. I also assume you do the following every time.
- Use the “all is in the headers” approach. In essence modern variant of “Single Compilation Unit” application composition method. In this scenario, the
__COUNTER__
macro will produce IDs unique to the level of application. Thus all the types of thedbj::static_matrix<T,R,C, DBJ_UID>
will contain unique IDs. - Use namespaces. In this scenario even if you do not use technique 1 you will have separate types because they will be adorned with namespaces.
static_assert( ! std::is_same_v <space_a::mx9 , space_b::mx9 >);
The two above or just the second technique will solve all the problems on the application level. In the very unlikely event, you have them.
Conclusion
As ever if anybody needs advice on how to use the “dbj matrix”, in her project please do let us know, in the comments below, or through any other modern communication channel available.
Enjoy the standard C++