在经常访问的大小、行和列是const成员而不是getter函数的情况下,可以创建一个2D矩阵类吗



我一直对使用getter访问矩阵维度感到恼火。当然可以有公共行和列,但它们应该是const,这样Matrix类的用户就不能直接修改它们。这是一种自然的编码方式,但由于使用const成员被认为(显然是错误的(在没有UB的情况下是不可能的,这导致人们在真正应该是常量的情况下使用非常量字段,这些常量在需要时是可变的,如调整大小或赋值。

我想要的是可以这样使用的东西:

Matrix2D<double> a(2, 3);
int index{};
for (int i = 0; i < a.rows; i++)
for (int ii = 0; ii < a.cols; ii++)
a(i, ii) = index++;

但其中CCD_ 2是不可编译的,因为它是常量。如果类可以包含在向量和其他容器中,那就太好了。

现在:实现C++20的P0784(更多constexpr容器(https://reviews.llvm.org/D68364?id=222943

它应该在没有强制转换或UB的情况下是可行的,甚至可以在计算常量表达式时完成。我相信c++20通过提供函数std::destroy_atstd::construct_at使它变得可行。我附上了一个使用c++20的答案,这似乎表明在类中提供const成员对象确实是可能的,而且很容易做到,而不会带来不必要的负担。

因此,问题是c++20中新的constexpr函数是否有效,这些函数提供了销毁然后构造具有不同const的对象的能力?它看起来确实如此,并且通过了UB测试的consexpr缺失。

如果你想拥有常量属性,你可以试试这个:

class Matrix {
public:
const int rows;
const int cols;

Matrix(int _rows, int _cols) : rows(_rows), cols(_cols)
{

}
};

Matrix实例的rowscols属性在构造函数中初始化一次。

好吧,这是我在2D矩阵中使用常量行和列的过程。它通过了UB测试,可以在向量等集合中使用,没有奇怪的限制。这是部分实现。无边界检查等等,但提供了一种解决问题的可能方法。

matrix2.h

#pragma once
#include <memory>
#include <vector>
#include <initializer_list>
#include <stdexcept>
#include <array>
template<class T>
class Matrix2D
{
std::vector<T> v;
public:
const size_t rows;
const size_t cols;
constexpr Matrix2D(const Matrix2D& m) = default;
constexpr Matrix2D(Matrix2D&& m) noexcept = default;
explicit constexpr Matrix2D(size_t a_rows=0, size_t a_cols=0) : v(a_rows* a_cols), rows(a_rows), cols(a_cols) {}
constexpr Matrix2D(std::initializer_list<std::initializer_list<T>> list) : rows(list.size()), cols((list.begin())->size())
{
for (auto& p1 : list)
for (auto p2 : p1)
v.push_back(p2);
}
constexpr Matrix2D& operator=(const Matrix2D& mat)
{
std::vector<T> tmp_v = mat.v;
std::construct_at(&this->rows, mat.rows);
std::construct_at(&this->cols, mat.cols);
this->v.swap(tmp_v);
return *this;
}
constexpr Matrix2D& operator=(Matrix2D&& mat) noexcept
{
std::construct_at(&this->rows, mat.rows);
std::construct_at(&this->cols, mat.cols);
this->v.swap(mat.v);
return *this;
}
// user methods
constexpr T* operator[](size_t row) {   // alternate bracket indexing
return &v[row * cols];
};
constexpr T& operator()(size_t row, size_t col)
{
return v[row * cols + col];
}
constexpr const T& operator()(size_t row, size_t col) const
{
return v[row * cols + col];
}
constexpr Matrix2D operator+(Matrix2D const& v1) const
{
if (rows != v1.rows || cols != v1.cols) throw std::range_error("cols and rows must be the same");
Matrix2D ret = *this;
for (size_t i = 0; i < ret.v.size(); i++)
ret.v[i] += v1.v[i];
return ret;
}
constexpr Matrix2D operator-(Matrix2D const& v1) const
{
if (rows != v1.rows || cols != v1.cols) throw std::range_error("cols and rows must be the same");
Matrix2D ret = *this;
for (size_t i = 0; i < ret.v.size(); i++)
ret.v[i] -= v1.v[i];
return ret;
}
constexpr Matrix2D operator*(Matrix2D const& v1) const
{
if (cols != v1.rows) throw std::range_error("cols of first must == rows of second");
Matrix2D v2 = v1.transpose();
Matrix2D ret(rows, v1.cols);
for (size_t row = 0; row < rows; row++)
for (size_t col = 0; col < v1.cols; col++)
{
T tmp{};
for (size_t row_col = 0; row_col < cols; row_col++)
tmp += this->operator()(row, row_col) * v2(col, row_col);
ret(row, col) = tmp;
}
return ret;
}
constexpr Matrix2D transpose() const
{
Matrix2D ret(cols, rows);
for (size_t r = 0; r < rows; r++)
for (size_t c = 0; c < cols; c++)
ret(c, r) = this->operator()(r, c);
return ret;
}
// Adds rows to end
constexpr Matrix2D add_rows(const Matrix2D& new_rows)
{
if (cols != new_rows.cols) throw std::range_error("cols cols must match");
Matrix2D ret = *this;
std::construct_at(&ret.rows, rows + new_rows.rows);
ret.v.insert(ret.v.end(), new_rows.v.begin(), new_rows.v.end());
return ret;
}
constexpr Matrix2D add_cols(const Matrix2D& new_cols)
{
if (rows != new_cols.rows) throw std::range_error("rows must match");
Matrix2D ret(rows, cols + new_cols.cols);
for (size_t row = 0; row < rows; row++)
{
for (size_t col = 0; col < cols; col++)
ret(row, col) = this->operator()(row, col);
for (size_t col = cols; col < ret.cols; col++)
ret(row, col) = new_cols(row, col - cols);
}
return ret;
}
constexpr bool operator==(Matrix2D const& v1) const
{
if (rows != v1.rows || cols != v1.cols || v.size() != v1.v.size())
return false;
for (size_t i = 0; i < v.size(); i++)
if (v[i] != v1.v[i])
return false;
return true;
}
constexpr bool operator!=(Matrix2D const& v1) const
{
return !(*this == v1);
}
// friends
template <size_t a_rows, size_t a_cols>
friend constexpr auto make_std_array(const Matrix2D& v)
{
if (a_rows != v.rows || a_cols != v.cols) throw std::range_error("template cols and rows must be the same");
std::array<std::array<T, a_cols>, a_rows> ret{};
for (size_t r = 0; r < a_rows; r++)
for (size_t c = 0; c < a_cols; c++)
ret[r][c] = v(r, c);
return ret;
}
};

来源.cpp

#include <vector>
#include <algorithm>
#include <iostream>
#include "matrix2d.h"
constexpr std::array<std::array<int, 3>, 4> foo()
{
Matrix2D<double> a(2, 3), b;
Matrix2D d(a);
int index{};
for (int i = 0; i < a.rows; i++)
for (int ii = 0; ii < a.cols; ii++)
a(i, ii) = index++;
b = a + a;
Matrix2D<int> z1{ {1},{3},{5} };
z1 = z1.add_cols({{2}, {4}, {6}});
z1 = z1.add_rows({{7,8}});
// z1 is now {{1,2},{3,4},{5,6},{7,8}}
Matrix2D<int> z2{ {1,2,3},{4,5,6} };
Matrix2D<int> z3 = z1 * z2;     // z3: 4 rows, 3 cols
// from separate math program product of z1 and z2
Matrix2D<int> ref{ {9,12,15},{19,26,33},{29,40,51},{39,54,69} };
// test transpose
Matrix2D<int> tmp = ref.transpose(); // tmp: now 3 rows, 4 cols
if (ref == tmp) throw;  // verify not equal
tmp = tmp.transpose();  // now ref==tmp==z3
if (ref != tmp || ref != z3) throw;
// Check using vector of matrixes, verify sort on row size works
std::vector<Matrix2D<int>> v;
v.push_back(z1);
v.push_back(z2);
v.push_back(z1);
v.push_back(z2);
std::sort(v.begin(), v.end(), [](auto const& m1, auto const& m2) {return m1.rows < m2.rows; });
for (size_t i = 0; i < v.size() - 1; i++)
if (v[i].rows > v[i + 1].rows) throw;
// return constexpr of 2D std::array
std::array<std::array<int, 3>, 4> array = make_std_array<4,3>(ref); // ref must have 4 rows, 3 cols
return array;
}

int main()
{
auto print = [](auto& a) {
for (auto& x : a)
{
for (auto y : x)
std::cout << y << " ";
std::cout << 'n';
}
std::cout << 'n';
};
// run time
auto zz = foo();
print(zz);
// validating no UB in Matrix2D;
// and creating a nested, std::array initialized at compile time
//constexpr std::array<std::array<int, 3>, 4>  x = foo();
constexpr std::array<std::array<int, 3>, 4>  x = foo(); // compile time
print(x);
return 0;
}

最新更新