如何尽量减少重复的模板类型名称?

3
这个片段是一个来自代码库的小例子。它是一个用于将矩阵相乘的自由函数。矩阵本身在ROWSCOLUMNS上进行了模板化处理,就像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

2
auto operator*(const Matrix<ROWS, COLUMNS>& lhs, decltype(lhs) rhs) - 273K
2
定义一个matrix的概念,然后类似于template <matrix LHS, std::same_as<LHS> RHS> LHS operator*(const LHS& lhs, const RHS& rhs) - 康桓瑋
那么,让维度成为静态成员。然后你可以写 for(LHS::size_type row = 0; row < lhs.rows; row++) ...。此外,使用 uint8_t 作为索引听起来像是等待溢出的事情。 - Passer By
你可以将其设置为友元并在类模板内进行定义。 - 463035818_is_not_a_number
最简单的方法是在类内部实现,因为在这种情况下,您可以简单地省略模板参数。您甚至不需要使用特定的C++标准来使其工作。请参见此处的代码:template <uint8_t ROWS, uint8_t COLUMNS> class Matrix{... friend Matrix operator*(Matrix const& lhs, Matrix const& rhs) { ... } };,请参阅此处(2):https://en.cppreference.com/w/cpp/language/friend - fabian
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>类型。这里需要一个类型特征(trait),检查传递的类型是否是Matrix类型。请注意保留HTML标签。
#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 或者 conceptoperator* 只能用于 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;
}

这肯定更易读,我相信它仍然会强制要求两个参数必须是相同类型/大小。

其中一个缺点可能是模板变得太放任了。 它也可能被实例化(并失败)为非矩阵类型。 有没有一种简单的方法(概念?)来将模板限制为仅类似于矩阵的类?

更新:JeJo's 答案 的帮助下,我能够拼凑出这个约束条件:

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

限制条件似乎起作用了。我不确定重复是否减少了很多,但它比我最初拥有的一堆尖括号更易读。


当然是可以的。尝试一下这里的某些解决方案:https://dev59.com/r1QJ5IYBdhLWcg3wSTx1 并更新您的答案,使用您认为适合此用例的解决方案。 - cigien
这仅适用于方阵。 - 463035818_is_not_a_number

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接