Search code examples
c++c++14variadic-templatestemplate-specializationtemplate-argument-deduction

How to initialise a constexpr matrix of type Matrix<T,Rows,Cols>?


I was not really able to summarize the problem in the question. I already made a similar question here.There I asked help for defining static constexpr matrices. The solution was to add another argument to the list of the template matrix, basically Matrix<T,Rows,Cols,std::make_index_sequence<Rows*Cols>>.

I accepted the answer, but later I noticed that with this version, my older code was not able to support the call of functions which have as argument a Matrix<T,Rows,Cols>, for example as:

foo(Matrix<T,Rows,Cols> & foo){...}

since the absence of the fourth implicit parameter was giving me a compile error, i.e. candidate template ignored: could not match '__make_integer_seq' against 'integer_sequence'.

  1. Can someone, please, explain to me why and what should I do to fix this? I guess it is fixable but I was not able to figure it out.

Then I found out that I can define the class in a different manner, maintaining the classic structure Matrix<T,Rows,Cols>, but still being able to define static constexpr matrices (I just added what is necessary here):

template<typename T, Integer Rows_, Integer Cols_>
class Matrix {
public:

    static constexpr Integer Rows=Rows_;
    static constexpr Integer Cols=Cols_;
    using type= Matrix<T,Rows,Cols>;
    using subtype=T;

    ~Matrix()=default; 

    Matrix() {}

    template<typename...Inputs>
          constexpr Matrix (const Inputs&...vals)
          : values{{ {static_cast<T>(vals)}...}}
          {static_assert(sizeof...(Inputs)==Rows*Cols, "...");}


private:

   std::array<T, Rows * Cols> values;
};

So with the static_cast() in the constructor I can define static matrices without changing the template of the Matrix class. I can do something as

 static constexpr Matrix< double, 2, 2> A{1.,2.,3.,4.};

but I can also maintain the call to functions like foo(Matrix<T,Rows,Cols> & foo){...}. So I was ok with this solution. But then I tried to create a matrix of matrices and I found out that this class version fails in the constructor of this kind:

Matrix< Matrix<double,1,1>, 2, 2> A{{0.1},{0.1},{0.1},{0.1}};

even though it succeeds if I first initialize the elements and then I pass them as argument:

    static constexpr Matrix< double, 1,1> a{{0.1}};
    static constexpr Matrix< Matrix<double,1,1>, 2, 2> A{a,a,a,a};

However, I would like to avoid this, if possible.

As a more clarifying example I will give this:

Matrix< Matrix<Real,1,1>, 2, 2> A{a,a,a,{0.1}};

which gives the following compile error:

candidate template ignored: substitution failure : deduced incomplete pack <Matrix<double, 1, 1>, Matrix<double, 1, 1>, Matrix<double, 1, 1>,
      (no value)> for template parameter 'Inputs'``` (so ```{0.1}``` is no value).

If instead of {0.1} I write the constructor Matrix<double,1,1>{0.1}, then it works, but it is horrible to look at.


  1. Why cannot I simply construct the elements of the matrix by means of the series of {0.1}? Is there any workaround for this?

Solution

    1. Can someone, please, explain to me why and what should I do to fix this? I guess it is fixable but I was not able to figure it out.

    I can't explain.

    I can confirm that clang++ gives the error but g++ compile without problem. I suspect a clang++ bug (or a not standard compliance) but I'm not an expert. I intend to simplify the problem and propose it as a separate question.

    How to fix? You can add a level of inheritance.

    You can create a Matrix_base class that make the trick of std::make_index_sequence/std::index_sequence

    template <typename, typename>
    class Matrix_base;
    
    template <typename T, std::size_t ... Is>
    class Matrix_base<T, std::index_sequence<Is...>>
     { 
       // values_ and constructors
     };
    

    and inherit it from a Matrix class whit only three template parameter

    template <typename T, std::size_t NR, std::size_t NC>
    class Matrix : public Matrix_base<T, std::make_index_sequence<NR*NC>>
     {
       public:
          using value_type = T;
    
          using MB = Matrix_base<T, std::make_index_sequence<NR*NC>>;
    
          using MB::MB;
          using MB::values_;
    
          // other methods
     };
    

    A complete example follows

    Why cannot I simply construct the elements of the matrix by means of the series of {0.1}? Is there any workaround for this?

    The problem is the type deduction.

    If you have a constructor

    template<typename...Inputs>
          constexpr Matrix (const Inputs&...vals)
          : values{{ {static_cast<T>(vals)}...}}
          {static_assert(sizeof...(Inputs)==Rows*Cols, "...");}
    

    that receive a variadic sequence of Input... argument, where the compiler must deduce the Inputs... types from the vals... values, you can't pass something as {0.1} (or maybe {0.1, 0.2, 0.3, 0.4}) as a value hoping that the compiler can deduce a type from it.

    Different if you have the original constructor

      constexpr Matrix (getType<value_type, Is> ... vals)
         : values_{{vals...}}
       {}
    

    where getType<value_type, Is> become value_type. You have a constructor that expect a sequence of sizeof...(Is) elements of a known type: value_type. So no type has to be deduced: when the Matrix constructor expects four element of type Matrix<double, 1u, 1u>, if you pass four argument {0.1} (or also four 0.1) the compiler know that {0.1} must be used to initialize a Matrix<double, 1u, 1u>.


    The following is a full compiling C++14 example

    #include <array>
    #include <type_traits>
    
    template <typename T, std::size_t>
    using getType = T;
    
    template <typename, typename>
    class Matrix_base;
    
    template <typename T, std::size_t ... Is>
    class Matrix_base<T, std::index_sequence<Is...>>
     {
       protected:
          std::array<T, sizeof...(Is)> values_{};
    
       public:
          constexpr Matrix_base (getType<T, Is> ... vals)
             : values_{{vals...}}
           {}
    
          constexpr Matrix_base (std::array<T, sizeof...(Is)> const & a)
             : values_{a}
           {}
    
          constexpr Matrix_base (std::array<T, sizeof...(Is)> && a)
             : values_{std::move(a)}
           {}
    
          constexpr Matrix_base () = default;
    
          ~Matrix_base() = default;
    
          constexpr Matrix_base (Matrix_base const &) = default;
          constexpr Matrix_base (Matrix_base &&) = default;
    
          constexpr Matrix_base & operator= (Matrix_base const &) = default;
          constexpr Matrix_base & operator= (Matrix_base &&) = default;
     };
    
    template <typename T, std::size_t NR, std::size_t NC>
    class Matrix : public Matrix_base<T, std::make_index_sequence<NR*NC>>
     {
       public:
          using value_type = T;
    
          using MB = Matrix_base<T, std::make_index_sequence<NR*NC>>;
    
          using MB::MB;
          using MB::values_;
    
          constexpr T const & operator() (std::size_t r, std::size_t c) const
           { return values_[r*NC+c]; }
    
          T & operator() (std::size_t r, std::size_t c)
           { return values_[r*NC+c]; }
    
          constexpr std::size_t rows () const
           { return NR; }
    
          constexpr std::size_t columns () const
           { return NC; }
     };
    
    template <typename T, std::size_t Dim1, std::size_t Dim2>
    void foo (Matrix<T, Dim1, Dim2> const &)
     { }
    
    int main()
     {
       static constexpr Matrix<double,2,2> staticmat{0.1,0.2,0.3,0.4};
    
       Matrix<Matrix<double,1,1>, 2, 2> a{{0.1}, {0.1}, {0.1}, {0.1}};
       Matrix<Matrix<double,1,1>, 2, 2> b{0.1, 0.1, 0.1, 0.1};
    
       foo(staticmat);
       foo(a);
       foo(b);
     }