Create a template meta-function to create an identity matrix at compile time

I was asking myself if it is possible to create a matrix unit at compile time if the size is known. So far I've just written an example of creating a matrix based on a std :: vector with a fixed size, eg. 4x4. However, I'm not sure how to set the values. I think I need recursion: /

// Example program
#include <iostream>
#include <string>
#include <vector>

template <class T>
using vec1D = std::vector<T>;

template <class T>
using vec2D = std::vector<std::vector<T>>;

template <class T, int size>
vec2D<T> make_mat() {
  vec2D<T> mat(size, vec1D<T>(size));
  return mat;
}

int main()
{
  vec2D<float> unit = make_mat<float, 4>();
  std::cout 
  << unit[0][0] << unit[0][1] << unit[0][2] << unit[0][3] << std::endl
  << unit[1][0] << unit[1][1] << unit[1][2] << unit[1][3] << std::endl
  << unit[2][0] << unit[2][1] << unit[2][2] << unit[2][3] << std::endl
  << unit[3][0] << unit[3][1] << unit[3][2] << unit[3][3] << std::endl;
}

      

+3


source to share


2 answers


I was asking myself if it is possible to create a matrix unit at compile time if the size is known.

I don't think if your matrix std::vector

.

But if it's based on std::array

and you want it to be zero-initialized (as in your example) yes: it's possible.

// Example program
#include <iostream>
#include <array>

template <typename T, std::size_t D1, std::size_t D2 = D1>
constexpr std::array<std::array<T, D2>, D1> doA ()
 { return { }; }

int main()
{
  constexpr auto unit = doA<float, 4U>();

  std::cout 
  << unit[0][0] << unit[0][1] << unit[0][2] << unit[0][3] << std::endl
  << unit[1][0] << unit[1][1] << unit[1][2] << unit[1][3] << std::endl
  << unit[2][0] << unit[2][1] << unit[2][2] << unit[2][3] << std::endl
  << unit[3][0] << unit[3][1] << unit[3][2] << unit[3][3] << std::endl;
}

      

But I don't think that's a good idea: assume you're std::array

using the stack, not the heap. So this solution is only useful for small (very small) matrices.

--- EDIT ---



OP (correctly) observes

the question was if init can initialize the matrix as the identity matrix: 100 010 001

Yes, it is possible with the identity matrix; a little more complicated, but possible.

// Example program
#include <iostream>
#include <utility>
#include <array>

template <typename T, std::size_t I, std::size_t ... Is>
constexpr auto doU_helper2 (std::index_sequence<Is...> const &)
 { return std::array<T, sizeof...(Is)>
    { { (Is == I ? T{1} : T{0})... } }; }

template <typename T, std::size_t ... Is>
constexpr auto doU_helper1 (std::index_sequence<Is...> const & is)
 { return std::array<std::array<T, sizeof...(Is)>, sizeof...(Is)>
    { { doU_helper2<T, Is>(is)... } }; }

template <typename T, std::size_t Dim>
constexpr auto doU ()
 { return doU_helper1<T>(std::make_index_sequence<Dim>{}); }

int main()
{
  constexpr auto unit = doU<float, 4U>();

  std::cout 
  << unit[0][0] << unit[0][1] << unit[0][2] << unit[0][3] << std::endl
  << unit[1][0] << unit[1][1] << unit[1][2] << unit[1][3] << std::endl
  << unit[2][0] << unit[2][1] << unit[2][2] << unit[2][3] << std::endl
  << unit[3][0] << unit[3][1] << unit[3][2] << unit[3][3] << std::endl;
}

      

Consider this code uses std::make_index_sequence{}

and std::index_sequence

, which are C ++ 14 features.

If you need a C ++ 11 solution, it's not too hard to create a subheading; ask if you need it (I did somewhere, I only need to get it).

+3


source


You cannot do dynamic allocation at compile time.

You can do this at runtime. The easy way is with loops.

If you want to avoid loops entirely:

template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>){
  return [](auto&&f)->decltype(auto){
    return decltype(f)(f)(std::integral_constant<std::size_t,Is>{}...);
  };
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t,N> ={}){
  return index_over(std::make_index_sequence<N>{});
}

template <class T>
using vec1D = std::vector<T>;

template <class T>
using vec2D = std::vector<std::vector<T>>;

template<std::size_t N, std::size_t I,class T>
vec1D<T> make_row( T const& base, T const& diag ){
  return index_upto<I>()([&](auto...Before){
    return index_upto<N-I-1>()([&](auto...After)->vec1D<T>{
      return {
        (void(Before), base)...,
        diag,
        (void(After), base)...
      };
    });
  });
}

template<std::size_t N, class T>
vec2D<T> make_mat( T const& base, T const& diag){
  return index_upto<N>()([&}(auto...Rs)->vec2D<T>{
    return {
      make_row<N, Rs>(base, diag)...
    };
  });
}

      

may contain typos (written by phone, not verified). Uses constructs that are legal C ++, but g ++ sometimes chokes; Klang will understand this. C ++ 14.

The before and after index avoids having to write a bunch of helper functions to extend the index parameter packages; they allow expansion to lambda.



If your compiler complains about Rs

which is being used at compile time, replace with decltype(Rs)::value

- your compiler has failed to evaluate constexpr

.

Doing this in C ++ 11 is annoying. I could use helper types to extend the package, one for each function I wrote above. You also want to rewrite the index sequence from C ++ 14. It's very meh, just a loop.

auto u = make_mat<4>( 0, 1 );

      

creates a int

4x4 diagonal matrix .

Note that this method, with liberal constexpr

size and type, works with std array. Unfortunately, lambda couldn't be constexpr until C ++ 17.

The loop solution also works with constexpr

post C ++ 14 matrices; you can mutate local data in constexpr.

+2


source







All Articles