0

I am trying to get a piece of code I am writing for fun working. Basically I'd like to generate a type, given at compile time: a matrix.

For instance, I'd like an abstract_matrix be something like the following:

template <std::size_t r, std::size_t c, class T = double>
class abstract_matrix
{
public:

    static const std::size_t rows = r;

    static const std::size_t cols = c;

    typedef T value_type;

    typedef T storage_type[rows][cols];

    constexpr abstract_matrix(storage_type items)
    {
        // I hope it's not needed
    }

    storage_type storage;
};

Then, I'd like to actually create concrete types with some magic, along the lines of these:

constexpr static const int vals[3][3] {
    { 1, 0, 0 },
    { 0, 1, 0 },
    { 0, 0, 1 }
};

// do magic here for the vals parameter to make this compile
using id_3by3 = abstract_matrix<3, 3, vals, int>;

// use the generated type
id_3by3 mymatrix;

auto matrix = mymatrix * ...;

Do you have any suggestions about how to "inject" those values into the template and generate a proper type at compile time? Everything in the type is static, const, and constexpr.

Thanks!

senseiwa
  • 2,369
  • 3
  • 24
  • 47
  • You want the values of the matrix to be part of its type? Why? Why not simply `abstract_matrix<3, 3, int> id_3by3(vals);`? – Thomas Nov 02 '17 at 16:49
  • You don't want to pass the `vals` in the template argument list. Look at `std::initializer_list`. It presents some issues with `constexpr`, though they are potentially being fixed. See: https://stackoverflow.com/questions/15937522/constexpr-array-and-stdinitializer-list . – Zalman Stern Nov 02 '17 at 16:51
  • 1
    Yes, you don't *really* want the element values to be part of the type, as that will quickly lead to unimaginable volumes of template bloat, for no reason that I can imagine, or perhaps even just crash your compiler once you get beyond a certain dimension. Perhaps you should explain *why* you *think* you want this, so that you can get more useful answers that 'don't do that'. – underscore_d Nov 02 '17 at 17:34
  • @underscore_d, I doubt it will lead to bloated code. Not all the values of the `vals` array become part of the template definition. Only the address of `vals` becomes part of the template definition. – R Sahu Nov 02 '17 at 17:36
  • @RSahu That seems like an assumption to me, stemming from the vagueness of the question. My assumption just happened to be different. – underscore_d Nov 02 '17 at 17:37
  • @underscore_d, I made that statement based on my answer. – R Sahu Nov 02 '17 at 17:38
  • @RSahu And here I was thinking comments on the question should be about the question, not some answer. While the question is vague, it's fair to speculate that the OP might not really want what they think they want, and why that might be. – underscore_d Nov 02 '17 at 17:40
  • There is no particular reason about this, just learning compile-time options that I can have at work. – senseiwa Nov 02 '17 at 19:24

1 Answers1

1

You can make it work but there are several things that must be considered.

  1. vals can be passed as a non-type parameter. However, it needs to come after T. Otherwise, the fundamental type of the parameter is not known.

    As a consequence, there is no default value of T. You could potentially continue to have default value of T as double and default value of the last argument as nullptr but that leads to messy code.

  2. Definition of id_3by3 needs to use &vals, not just vals.

  3. If you define vals to be const, then the non-type template parameter must also use const in its type.

    const int vals[3][3] { ... };
    

    mandates that you use

    template <std::size_t r, std::size_t c, class T, T const(*v)[r][c] >
    class abstract_matrix { ... };
    

    If you would like to be able to modify the contents of the matrix, you need to use:

    template <std::size_t r, std::size_t c, class T, T (*v)[r][c]>
    class abstract_matrix { ... };
    

    which mandates that you use

    int vals[3][3] { ... };
    

Here's a working program:

#include <iostream>
#include <cstdint>

template <std::size_t r, std::size_t c, class T , T (*v)[r][c]>
class abstract_matrix
{
public:

    static const std::size_t rows = r;

    static const std::size_t cols = c;

    typedef T value_type;

    constexpr static T(&vals)[r][c] = *v;

    constexpr abstract_matrix()
    {
    }
};

int vals[3][3] {
    { 1, 0, 0 },
    { 0, 1, 0 },
    { 0, 0, 1 }
};

using id_3by3 = abstract_matrix<3, 3, int, &vals>;

int main()
{
   id_3by3 mymatrix;

   // Original matrix.
   for ( size_t i = 0; i < id_3by3::rows; ++i )
   {
      for ( size_t j = 0; j < id_3by3::cols; ++j )
      {
         std::cout << id_3by3::vals[i][j] << " ";
         id_3by3::vals[i][j]++;
      }
      std::cout << "\n";
   }
   std::cout << "\n";

   // Modified matrix.
   for ( size_t i = 0; i < id_3by3::rows; ++i )
   {
      for ( size_t j = 0; j < id_3by3::cols; ++j )
      {
         std::cout << id_3by3::vals[i][j] << " ";
      }
      std::cout << "\n";
   }
}

Output:

1 0 0
0 1 0
0 0 1

2 1 1
1 2 1
1 1 2

Update, in response to OP's commment

You can put use static int vals[3][3] = { ... }; when you want to be able define the type a .h file and use it in multiple .cpp files.

I was able to use a .h file with the following content in multiple .cpp files.

#pragma once
#include <cstdint>

template <std::size_t r, std::size_t c, class T , T (*v)[r][c]>
class abstract_matrix
{
   public:

      static const std::size_t rows = r;

      static const std::size_t cols = c;

      typedef T value_type;

      constexpr static T(&vals)[r][c] = *v;

      constexpr abstract_matrix()
      {
      }
};

static int vals[3][3] {
   { 1, 0, 0 },
   { 0, 1, 0 },
   { 0, 0, 1 }
};

using id_3by3 = abstract_matrix<3, 3, int, &vals>;

That can still be improved by dividing those lines into two .h files. E.g., you could use:

abstract_matrix.h:

#pragma once
#include <cstdint>

template <std::size_t r, std::size_t c, class T , T (*v)[r][c]>
class abstract_matrix
{
   public:

      static const std::size_t rows = r;

      static const std::size_t cols = c;

      typedef T value_type;

      constexpr static T(&vals)[r][c] = *v;

      constexpr abstract_matrix()
      {
      }
};

id_3by3.h:

#include "abstract_matrix.h"

static int vals[3][3] {
   { 1, 0, 0 },
   { 0, 1, 0 },
   { 0, 0, 1 }
};

using id_3by3 = abstract_matrix<3, 3, int, &vals>;

That will allow you to define other types similar to id_3by3 in different .h files without needing to duplicate the definition of abstract_matrix in all of them.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Awesome! How can I avoid having multiple symbols definitions if I put this in my headers? I've tried static, const, constexpr, and different combinations, but either I cannot compile it (non type template parameter) or I have multiple definitions and so linker errors. – senseiwa Nov 03 '17 at 10:19
  • Thanks! The only warning I would give about this code is that, at least for me, it compiles with C++17 and above. – senseiwa Nov 07 '17 at 07:06
  • @senseiwa. that's strange. I was able to build the program using g++/C++11. – R Sahu Nov 07 '17 at 07:09