如何最大程度地减少重复的模板类型名称?

问题描述 投票:0回答:2

此片段是来自 代码库的一个小示例。它是一个自由函数,可以将矩阵相互相乘。矩阵本身在

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()
    ),因此我们不需要在这些循环中使用模板参数。
  • Matrix 还提供了
    ::size_type
    ,因此理想的解决方案是让我们使用它(干净地)而不是在循环中硬编码
    uint8_t
c++ templates coding-style c++20 function-templates
2个回答
3
投票

现代 C++ 中有哪些语法或策略可以使重复的类型名称不那么嘈杂?

实现此目的的最简单方法是使

operator*
成为
friend
函数,并在类本身内部完成实现。

但是,如果您坚持在课堂外进行,则可以通过使用 缩写函数模板(自 起)来消除所示代码的嘈杂部分。

#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
类型。

查看实时示例代码


0
投票

一种替代方法是使用

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;
}

这个约束似乎起作用了。我不确定重复次数是否减少了很多,但它比我最初的尖括号森林更具可读性。

© www.soinside.com 2019 - 2024. All rights reserved.