Создание структуры массива во время компиляции (c ++ 17) - PullRequest
0 голосов
/ 01 февраля 2019

У меня есть следующий код C ++ 17, чтобы сгенерировать кортеж во время компиляции, где нулевые массивы приведены только для примера, и в моей реализации они будут полными (скомпилируйте с -std = c ++ 1z -fconcepts).

#include <array>
#include <tuple>
#include <cmath>

template <std::size_t nrA, std::size_t ncA, std::size_t nrB, std::size_t ncB,
          typename number=double>
constexpr auto operator *(const std::array<std::array<number,ncA>,nrA> & A,
                          const std::array<std::array<number,ncB>,nrB> & B)
{
  std::array<std::array<number,ncB>,nrA> res{} ;
  for (auto k=0u;k<ncB;++k)
    for (auto i=0u;i<nrA;++i)
      for (auto j=0u;j<nrB;++j)
        res[i][k] += A[i][j]*B[j][k];
  return res ;
}

constexpr auto logg2(const auto N)
{
  auto res = 0;
  auto n = N;
  while (n != 0) 
  {
    n /= 2;
    ++res;
  }
  return res;
}

template <std::size_t N,typename number=double>
constexpr auto create_R()
{
  return std::array<std::array<double,2*N>,N>{};
}

template <std::size_t N,typename number=double>
constexpr auto create_RT()
{
  return std::array<std::array<double,N>,2*N>{};
}

template <std::size_t N,std::size_t ...Is>
constexpr auto make_impl(const std::index_sequence<Is...>)
{
  return std::make_tuple(std::make_tuple(create_R<(N >> Is)>()...),
                         std::make_tuple(create_RT<(N >> Is)>()...));
}

template <std::size_t N,typename number=double>
constexpr auto make()
{
  return make_impl<N/2>(std::make_index_sequence<logg2(N/2) - 1>());
}

int main(int argc, char *argv[])
{
  const auto n = 4u;
  const auto A = std::array<std::array<double,2*n>,2*n>{};
  const auto [R,RT] = make<2*n>();
}

Я хотел бы изменить make<>() на make<>(A) и вернуть структурированное связывание [R,RT,As], где As - кортеж, содержащий в себе следующие массивы

                              A,
               std::get<0>(R)*A*std::get<0>(RT),
std::get<1>(R)*std::get<0>(R)*A*std::get<0>(RT)*std::get<1>(RT)
                             ...

Я пытался некоторое время и не нашел решения.

Есть идеи?

Редактировать 1

В соответствии с запросом @MaxLanghof, следующая матрица печатает:

template <std::size_t nr, std::size_t nc, typename number=double>
constexpr auto print(const std::array<std::array<number,nc>,nr> & A)
{
  for (auto i=0u;i<nr;++i)
    {
      for (auto j=0u;j<nc;++j)
        std::cout << std::right << std::setw(12) << A[i][j];
      std::cout << std::endl ;
    }
  std::cout << std::endl ;
}

и добавляя следующие строки к main()

print(A);
print(std::get<0>(R)*A*std::get<0>(RT));
print(std::get<1>(R)*std::get<0>(R)*A*std::get<0>(RT)*std::get<1>(RT));

, получая следующий вывод

       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0
       0           0           0           0           0           0           0           0

       0           0           0           0
       0           0           0           0
       0           0           0           0
       0           0           0           0

       0           0
       0           0

1 Ответ

0 голосов
/ 01 февраля 2019

Решение включает в себя создание другой индексной последовательности для каждой записи в кортеже As, а затем ее использование для складывания и умножения.Я взял на себя смелость обернуть std::array<std::array<T, Cols>, Rows> в тип для удобочитаемости (что также оказалось необходимым, см. Ниже).Каждый вызов makeMul приводит к одному из As элементов кортежа (оригинал A добавляется отдельно).

template <std::size_t Rows, std::size_t Cols, typename T = double>
struct Mat2D : std::array<std::array<T, Cols>, Rows> {};

template <class T_Rs, std::size_t... Is>
constexpr auto mult_lhs(T_Rs Rs, std::index_sequence<Is...>) {
  return (std::get<sizeof...(Is) - Is - 1>(Rs) * ...);
}

template <class T_RTs, std::size_t... Is>
constexpr auto mult_rhs(T_RTs RTs, std::index_sequence<Is...>) {
  return (std::get<Is>(RTs) * ...);
}

template <class T_A, class T_Rs, class T_RTs, std::size_t... Is>
constexpr auto makeMul_impl(T_A A, T_Rs Rs, T_RTs RTs,
                            std::index_sequence<Is...> is) {
  return mult_lhs(Rs, is) * A * mult_rhs(RTs, is);
}

template <std::size_t Index, class T_A, class T_Rs, class T_RTs>
constexpr auto makeMul(T_A A, T_Rs Rs, T_RTs RTs) {
  return makeMul_impl(A, Rs, RTs, std::make_index_sequence<Index + 1>());
}

template <std::size_t N, std::size_t... Is, typename T = double>
constexpr auto make_impl(const Mat2D<2 * N, 2 * N, T>& A,
                         std::index_sequence<Is...>) {
  auto Rs = std::make_tuple(create_R<(N >> Is)>()...);
  auto RTs = std::make_tuple(create_RT<(N >> Is)>()...);
  auto As = std::make_tuple(A, makeMul<Is>(A, Rs, RTs)...);
  return std::make_tuple(Rs, RTs, As);
}

template <std::size_t N, typename T = double>
constexpr auto make(const Mat2D<N, N, T>& A) {
  return make_impl<N / 2>(A, std::make_index_sequence<logg2(N / 2) - 1>());
}

int main(int argc, char* argv[]) {
  const auto n = 4u;
  const auto A = Mat2D<2 * n, 2 * n, double>{};
  const auto [Rs, RTs, As] = make(A);
}

Демо

ЭтоВажно отметить, что перегрузка операторов для типов std является проблемой здесь, по крайней мере с clang (что более строго следует стандарту): как только вы попытаетесь использовать перегруженный operator* в шаблоне, он не будетобнаружен, потому что ADL ищет его в namespace std (у меня изначально был Mat2D в качестве псевдонима вместо struct наследующего материала) - и вам не разрешено добавлять материал в namespace std (за исключением нескольких конкретных точек настройки).По крайней мере, именно так я понимаю эту ошибку.

Наследование от std типов в целом довольно ужасно, но я бы предположил, что ваша матрица на самом деле является определяемым пользователем типом на практике, так чтоничего из этого не будет иметь значения.

Наконец, я бы настоятельно рекомендовал давать настоящие имена вашим типам кортежей.Когда у вас есть кортежи кортежей (массивов массивов), каждому читателю придется тратить значительное количество времени, даже разбираясь в коде.Это уже помогло бы, если бы вы, например, сгруппировали каждый R и RT в структуру:

template<std::size_t N, typename T = double>
struct R_RT {
  Mat2D<N, 2 * N, T> R;
  Mat2D<2 * N, N, T> RT;
};

и / или имели, например,

template<class TupleOfR, class TupleOfRT, class TupleOfAs>
struct Rs_RTs_As {
  TupleOfR Rs;
  TupleOfRT RTs;
  TupleOfAs As;
};

Даже если это технически позволило былюбые три типа, он по-прежнему документирует то, что вы должны ожидать найти там, и с концепциями вы действительно можете правильно ограничить все (включая такое удовольствие, как «As будет иметь еще один элемент, чем Rs и RTs»,что, вероятно, понадобится большинству читателей, чтобы понять из чистого кода кортежа).

...