此片段是来自 c++20 代码库的一个小示例。它是一个自由函数,可以将矩阵相互相乘。矩阵本身在
ROWS
和 COLUMNS
上进行了模板化,与 std::array
一样,在函数接口中使用起来有点痛苦。
完整的类型名称变成
Matrix<ROWS, COLUMNS>
本身很好,但是当重复三次(对于返回值和两个参数)时,它确实会损害函数接口的可读性。
现代 C++ 中有哪些语法或策略可以使重复的类型名称不那么嘈杂?
template <uint8_t ROWS, uint8_t COLUMNS>
Matrix<ROWS, COLUMNS> operator*(
const Matrix<ROWS, COLUMNS>& lhs,
const Matrix<ROWS, COLUMNS>& rhs) noexcept
{
Matrix<ROWS, COLUMNS> result;
for (uint8_t row = 0; row < ROWS; ++row)
{
for (uint8_t col = 0; col < COLUMNS; ++col)
{
for (uint8_t i = 0; i < COLUMNS; ++i)
{
result(row, col) += lhs(row, i) * rhs(i, col);
}
}
}
return result;
}
要求:
.columns()
,.rows()
),因此我们不需要在这些循环中使用模板参数。::size_type
,因此理想的解决方案是让我们使用它(干净地)而不是在循环中硬编码 uint8_t
。现代 C++ 中有哪些语法或策略可以使重复的类型名称不那么嘈杂?
实现此目的的最简单方法是使
operator*
成为 friend
函数,并在类本身内部完成实现。
但是,如果您坚持在课堂外进行,则可以通过使用 缩写函数模板(自 c++20 起)来消除所示代码的嘈杂部分。
#include <type_traits> // std::decay_t
auto operator*(const auto& lhs, const auto& rhs) noexcept
{
// static assert for type check!
static_assert(std::is_same_v<std::decay_t<decltype(lhs)>
, std::decay_t<decltype(rhs)>>, "are not same type!");
std::decay_t<decltype(lhs)> result;
for (auto row = 0u; row < lhs.rows(); ++row) {
for (auto col = 0u; col < lhs.columns(); ++col) {
for (auto i = 0u; i < lhs.columns(); ++i) {
// calculate result
}
}
}
return result;
}
话虽如此,现在
operator*
接受任何类型相同的参数。但是,我们只需要 Matrix<ROWS, COLUMNS>
类型。类型特征,用于检查传递的类型,属于 Matrix
类型,在这里有帮助。
#include <type_traits> // std::false_type, std::true_type
template <typename> struct is_Matrix final : std::false_type{};
template <std::size_t ROWS, std::size_t COLUMNS>
struct is_Matrix<Matrix<ROWS, COLUMNS>> final : std::true_type {};
// or variable template
// template <typename> inline constexpr bool is_Matrix = false;
// template <std::size_t ROWS, std::size_t COLUMNS>
// inline constexpr bool is_Matrix<Matrix<ROWS, COLUMNS>> = true;
现在使用该特征,您可以限制(即SFINAE或
static_assert
或concept
)operator*
仅用于Matrix
类型。
一种替代方法是使用
auto
返回类型,并将模板参数更改为完整类型。像这样的东西:
template <class Matrix>
constexpr auto operator*(const Matrix& lhs, const Matrix& rhs) noexcept {
using size_type = Matrix::size_type;
Matrix result;
for (size_type row = 0; row < Matrix::HEIGHT; ++row) {
for (size_type col = 0; col < Matrix::WIDTH; ++col) {
for (size_type i = 0; i < Matrix::HEIGHT; ++i) {
result(row, col) += lhs(row, i) * rhs(i, col);
}
}
}
return result;
}
这当然更具可读性,并且我相信它仍然会强制两个参数必须具有相同的类型/大小。
这样做的一个缺点可能是模板变得过于杂乱。对于非 Matrix 类型,它也可以实例化(并失败)。有没有一种简单的方法(概念?)将模板限制为仅类似于矩阵的类?
更新: 在 JeJo's answer 的帮助下,我能够将这个约束拼凑在一起:
template<typename> constexpr bool isMatrix = false;
template <std::size_t ROWS, std::size_t COLUMNS>
constexpr bool isMatrix<Matrix<ROWS, COLUMNS>> = true;
using Matrix4 = Matrix<4, 4>;
static_assert(isMatrix<Matrix4>, "Constraint test failed. Matrix4 should be identified as a Matrix");
static_assert(!isMatrix<Tuple>, "Constraint test failed. Tuple shouldn't be identified as a Matrix.");
template <class Matrix>
requires (isMatrix<Matrix>)
constexpr auto operator*(const Matrix& lhs, const Matrix& rhs) noexcept {
using size_type = typename Matrix::size_type;
Matrix result;
for (size_type row = 0; row < Matrix::ROWS; ++row) {
for (size_type col = 0; col < Matrix::COLUMNS; ++col) {
for (size_type i = 0; i < Matrix::COLUMNS; ++i) {
result(row, col) += lhs(row, i) * rhs(i, col);
}
}
}
return result;
}
这个约束似乎起作用了。我不确定重复次数是否减少了很多,但它比我最初的尖括号森林更具可读性。