C++ 运算符修改/元编程策略,用于不那么冗长的语法



我现在正在C++学习模板元编程和表达式模板,因此作为练习,我正在创建一个线性代数库来练习我正在学习的概念。

到目前为止,我的库有一个完整的非成员运算符重载列表,用于所有可以重载的二进制运算符,并且有一个相当光滑的接口,易于扩展。 然而,我遇到的一个问题是矩阵运算通常有多种变体。 例如,对于乘法,有一般矩阵乘法、点积、克罗内克乘积、哈达马尔积、叉积等。

Matlab中使用的一种巧妙方法是用于hadamard乘法(以及.^,./等(的.*运算符。 在这种情况下,Matlab 语言使用 .运算符作为 * 运算符的修饰符。 但是,我不知道 c++ 语言中有任何机制允许像这样修改运算符。 此行为是否有任何干净的解决方法?

以下是我已经考虑过的一些事情:

  • 运算符重载允许额外的模板参数。 但是,我不完全确定在这种情况下如何利用这一点。 例如,一些可能很好的东西(尽管在实践中,我不确定是否有有效的语法来实现这一点(:
template<typename lhs_t, typename rhs_t, typename op_t = Gemm>
auto operator*(lhs_t lhs, rhs_t rhs)
{
...
}
// Operator template specializations ...
int main()
{
Matrix<double, 2, 2> mat1(1.0, 2.0, 3.0, 4.0);
Matrix<double, 2, 2> mat2(1.0, 2.0, 3.0, 4.0);
mat1 * mat2; // Works
mat1 *<Hadamard> mat2; // Error!  Syntax????
}
  • 使用 SFINAE/Concepts/if constexpr 和 traits 来修改二进制表达式类型或包装二进制表达式类型。 语法:
Hadamard(mat1 * mat2); // Hadamard wraps or modifies binary expression created by operator*
// SFINAE or Concepts used to select the correct routine based on the trait set
  • 创建一个免费的二进制函数。 可能的语法:
Hadamard<Multiplication>(mat1, mat2);
Hadamard_Multiplication(mat1, mat2);
  • 使用成员函数。语法:
mat1.hadamard_multiplication(mat2);

这些似乎都没有像Matlab那样优雅的语法:

mat1 .* mat2;

是否有任何技术可用于接近我可以考虑的运算符修饰符语法? 或者任何通用技术可以使使用语法不那么冗长? 如果没有,是否有任何概念可以包含在C++的未来版本中,这些内容可能在这里有任何用处?

这是我对此的认识:

  1. 可以支持多种语法。 我不需要选择一个,我只需要实现一种方式,并在顶部添加抽象模仿其他语法,如果需要的话。
  2. Hadamard 乘法(和除法等(是数组的默认乘法样式,而 GEMM 是矩阵的默认乘法模式。

对于这两项,我选择实现一个 n 维数组类(由于 CRTP 基类和自由函数用于实现,这仅仅是提供一个具有必要特征/别名声明的空结构的问题(。

然后,以下情况可能导致哈达玛乘法:

  1. 矩阵 A 与矩阵 B 具有相同的维度,并且两个矩阵都不是方阵,那么很明显 gemm 无效,但哈达玛乘法是。 因此,使用概念来覆盖此行为是有意义的。 换句话说:

    // Results in hadamard multiplication
    template<object_type lhs_t, object_type rhs_t> requires 
    (is_same_dimensions_v<lhs_t, rhs_t> && !is_special_mult_supported_v<lhs_t, rhs_t>)
    constexpr auto operator*(lhs_t&& lhs, rhs_t&& rhs) noexcept -> mul_expr<lhs_t, rhs_t>
    {
    return { std::forward<lhs_t>(lhs), std::forward<rhs_t>(rhs) };
    }
    // Results in general matrix multiplication
    template<typename lhs_t, typename rhs_t> requires is_gemm_supported_v<lhs_t, rhs_t>
    constexpr auto operator*(lhs_t&& lhs, rhs_t&& rhs) noexcept -> gemm_expr<lhs_t, rhs_t>
    {
    return { std::forward<lhs_t>(lhs), std::forward<rhs_t>(rhs) };
    }
    
  2. 如果将 gemm 表达式分配给数组,并且哈达玛乘法有效,则会将其隐式转换为哈达玛乘法。

    // implicit conversion to hadamard multiplication
    array = a * b;
    
  3. Gemm 表达式可以显式转换为 hadamard 表达式。

    // explicitly create a hadamard multiplication expression
    auto c = static_cast<mul_expr>(a * b);
    
  4. Gemm 表达式可以显式转换为数组,从而产生哈达玛乘法

    // explicitly create an array using hadamard multiplication
    auto c = static_cast<array>(a * b);
    
  5. 对于同时支持 hadamard 和 gemm 的混合数组/矩阵类型,则选择左侧类型以防止歧义。

    // if a is an array, and b is a matrix, then c is a mult_expr
    // if a is a matrix, and b is an array, then c is a gemm_expr
    auto c = a * b;
    

有了这些规则,就可以添加 API 级抽象来说明问题:

// c = hadamard(a * b);
template<expr_type expr_t> requires std::is_convertible_v<std::decay_t<expr_t>, mul_expr>
constexpr auto hadamard(expr_t&& expr) noexcept
{
return static_cast<mul_expr>(expr);
}
// support c = hadamard_mult(a, b); syntax
template<object_type lhs_t, object_type rhs_t> requires is_same_dimensions_v<lhs_t, rhs_t>
constexpr auto hadamard_mult(lhs_t&& lhs, rhs_t&& rhs) noexcept
{
return hadamard(lhs * rhs);
}

请注意,我省略了static_casts并转述了一些语法来表达这个想法。 这里要带走的最大认识是,c++可以利用类型系统来相当彻底地简化语法,这就是matlab的不同之处。 在很多情况下,

c = a * b;

可以(并且应该(导致哈达马尔乘法。 此外,类型系统的简单操作很容易导致很容易支持函数语法的情况。

非常感谢上面评论中的那些人帮助我集思广益并得出这个结论。 由于这里的讨论,我对我的图书馆非常满意。

最新更新