简体   繁体   English

C++ 运算符修改/元编程策略,以减少冗长的语法

[英]C++ Operator Modification/Metaprogramming Strategies for Less-Verbose Syntax

I am learning about template meta programming and expression templates in C++ right now, so as an exercise, I am creating a linear algebra library to practice the concepts I am learning.我现在正在学习 C++ 中的模板元编程和表达式模板,因此作为练习,我正在创建一个线性代数库来练习我正在学习的概念。

So far, my library has a complete list of non-member operator overloads for all of the binary operators that can be overloaded, and has a fairly-slick interface that's easily expandable.到目前为止,我的库有一个完整的非成员运算符重载列表,用于所有可以重载的二元运算符,并且有一个相当漂亮的界面,很容易扩展。 One problem I've run into, however, is that matrix operations often have multiple variations.然而,我遇到的一个问题是矩阵运算通常有多种变体。 For example, for multiplication, there is the general matrix multiplication, the dot product, the kroenecker product, the hadamard product, the cross product, and more.例如,对于乘法,有一般矩阵乘法、点积、克罗内克积、哈达玛积、叉积等等。

One slick way around this that is employed in Matlab is the .* operator used for hadamard multiplication (and .^, ./, etc).在 Matlab 中使用的一种巧妙的方法是用于 hadamard 乘法(和 .^、./ 等)的 .* 运算符。 In this case, the Matlab language employs the .在这种情况下,Matlab 语言使用 . operator as a modifier for the * operator.运算符作为 * 运算符的修饰符。 However, I'm unaware of any mechanisms in the c++ language that allow operators to be modified like this.但是,我不知道 C++ 语言中有任何允许像这样修改运算符的机制。 Are there any clean workarounds for this behavior?这种行为是否有任何干净的解决方法?

Here are some things I've thought about already:以下是我已经考虑过的一些事情:

  • operator overloads allow extra template parameters.运算符重载允许额外的模板参数。 However, I'm not entirely sure how to take advantage of this in this context.但是,我不完全确定如何在这种情况下利用这一点。 For instance, something that might be nice (though, in practice, I am not sure there is a valid syntax to achieve this):例如,一些可能很好的东西(尽管在实践中,我不确定是否有有效的语法来实现这一点):
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????
}
  • Using SFINAE/Concepts/if constexpr and traits to modify binary expression types or wrap binary expression types.使用 SFINAE/Concepts/if constexpr 和 traits 来修改二进制表达式类型或包装二进制表达式类型。 Syntax:句法:
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
  • Create a free binary function.创建一个免费的二元函数。 Possible syntaxes:可能的语法:
Hadamard<Multiplication>(mat1, mat2);
Hadamard_Multiplication(mat1, mat2);
  • Using member functions.使用成员函数。 Syntax:句法:
mat1.hadamard_multiplication(mat2);

None of these seem to have syntax quite as elegant as Matlab's:这些似乎都没有像 Matlab 那样优雅的语法:

mat1 .* mat2;

Are there any techniques available to come close to an operator modifier syntax that I can consider?是否有任何技术可以接近我可以考虑的运算符修饰符语法? Or any general techiques to make the usage syntax less verbose?或者任何使使用语法不那么冗长的通用技术? If not, is there any notion that something could be included in future versions of C++ that may be of any use here?如果没有,是否有任何想法可以在 C++ 的未来版本中包含一些可能在这里有用的东西?

Here is the realization that I came to with respect to this:这是我对此的认识:

  1. Multiple syntaxes can be supported.可以支持多种语法。 I do not need to pick one, I just need to implement one way and add abstractions over the top mimick the other syntaxes if so desired.我不需要选择一个,我只需要实现一种方式并在顶部添加抽象,如果需要的话模仿其他语法。
  2. Hadamard multiplication (and division, etc) are the default multiplication style for arrays, while GEMM is the default multiplication mode for matrices. Hadamard 乘法(和除法等)是数组的默认乘法样式,而 GEMM 是矩阵的默认乘法模式。

With those two items, I chose to implement an n-dimensional array class (since CRTP base classes and free functions are used for the implementation, this was simply a matter of providing an empty struct with the necessary traits/alias declarations).有了这两项,我选择实现一个 n 维数组类(因为 CRTP 基类和自由函数用于实现,这只是提供一个带有必要特征/别名声明的空结构的问题)。

The following situations can then result in hadamard multiplications:以下情况会导致哈达玛乘法:

  1. Matrix A has the same dimensions as Matrix B, and neither matrix is a square matrix, it is then clear that gemm is not valid, but hadamard multiplication is.矩阵 A 与矩阵 B 具有相同的维度,并且两个矩阵都不是方阵,那么显然 gemm 无效,但 hadamard 乘法有效。 Therefore, it makes sense to use a concept to override this behavior.因此,使用概念来覆盖此行为是有意义的。 in other words:换句话说:

     // 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. If a gemm expression is assigned to an array, and a hadamard multiplication is valid, it is implicitly converted to a hadamard multiplication.如果将 gemm 表达式分配给数组,并且 hadamard 乘法有效,则它会隐式转换为 hadamard 乘法。

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

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

    // explicitly create an array using hadamard multiplication auto c = static_cast<array>(a * b);
  5. In the case of mixed array/matrix types where both hadamard and gemm are supported, the left-hand side type is selected to prevent ambiguity.在同时支持 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;

With these rules in place, then api-level abstractions can be added for clarity:有了这些规则,为了清楚起见,可以添加 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);
    }

Note that I've omitted static_casts and paraphrased some of the syntax to get the idea across.请注意,我已经省略了 static_casts 并解释了一些语法来理解这个想法。 The big realization to take away here is that c++ can utilize the type system to simplify syntax fairly drastically, which is where matlab differs.这里要实现的一个重要实现是,c++ 可以利用类型系统来相当彻底地简化语法,这就是 matlab 的不同之处。 There are many cases where有很多情况

    c = a * b;

can (and should) result in hadamard multiplication.可以(并且应该)导致哈达玛乘法。 Furthermore, simple manipulation of the type system can quite easily result in situations where function syntax is easily supported.此外,对类型系统的简单操作很容易导致容易支持函数语法的情况。

A big thank you to those of you in the comments above for helping me brainstorm and arrive at this conclusion.非常感谢上面评论中的你们帮助我集思广益并得出这个结论。 I am quite satisfied with my library as a consequence of the discussion here.由于这里的讨论,我对我的图书馆非常满意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM