Why yet another Matrix?
Here is why I did make it.
In the standard C++ community 2d arrays are the source of endless ongoing debate. Especially the large ones created on the heap. While at the same time, approx 90% of use cases, in standard programs, do require, a plain old 2d array
1 2 |
// C/C++ plain old data structure int matrix[3][3]; |
But, It is a bit dangerous to let your team use this “naked”. It is not that comfortable if you use them regularly and it is dangerous since the stack space can deplete very fast.
As visible in the code behind this API, standard C++ is giving us quite a few mechanisms and features to encapsulate the rules of using such simple native matrices. Making it
Fast Safe and Comfortable
Actual usage might be a bit unusual because instances are not necessary. Just the types. Example
1 2 3 4 5 6 7 8 9 |
// require matrix as a type // the rest as usual template< typename MX, typename T > void test_mx ( T new_val) { // printarr is a method on // the stack_matrix type MX::printarr(your_print_function); } |
To use the test function declared above, we pass our matrix as a type and the rest of the arguments as value:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
constexpr size_t R = 3, C = 3; using mx9a = stack_matrix<int, R, C, DBJ_UID >; using mx9b = stack_matrix<int, R, C, DBJ_UID >; // make sure they are different // at compile time static_assert( mx9a::uuid() != mx9b::uuid() ); // us the function above // pass the matrix // as template argument test_mx<mx9a>( 0 ); test_mx<mx9b>( 100 ); |
This is a very fast solution
Above means, there are no copy or move mechanisms being used whatsoever, No complex, computation-intensive, matrix copying or swapping. The matrix simply stays in one place, where it was first created. Much like std::array
conceptually works.
If one needs, to pass instances of this matrix in and out of function, as values, this is perfectly unnecessary but perfectly doable:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// note: we receive argument as value // we return the matrix as value // none of it does matter. // There are no instance // data inside, compiler has no job // to try copy/move elision // there is nothing to copy or move template< typename MX> MX test_mx_arg_retval(MX the_mx) { // change a cell the_mx.data( the_mx.rows() - 1, the_mx.cols() - 1) = 1234; // for compiler this is // "zero effort" operation return the_mx; } |
And the usage of the above function follows:
1 2 3 4 5 6 7 |
// yes static_matrix has a default constructor // which does nothing by the way mx9a mxa = test_mx_arg_retval(mx9a()); mx9b mxb = test_mx_arg_retval(mx9b()); mxa.printarr(your_print_function); mxb.printarr(your_print_function); |
We receive the argument as value, we return the matrix as value. It totally 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 are necessary, get used to it :)
There are more usage samples, all in the testing code provided.
The ‘your_print_function’ from above (same as dbj::console::print) is this
1 2 3 4 5 6 7 8 9 10 11 |
inline auto your_print_function = []( auto const & first_param, auto const & ... params) { std::cout << first_param ; // if there are more params if constexpr (sizeof...(params) > 0) { // recurse print(params...); } return print; }; |