【问题标题】:Concept polymorphism in C++C++中的概念多态性
【发布时间】:2019-03-22 16:08:40
【问题描述】:

简短的问题是:如何使模板化函数表现不同,基于 关于参数概念支持。

我的意思是,如果类型 T 实现了某个概念,那么我的函数应该对它进行特殊处理,而不是其他类型。

问题扩展是:应该如何声明这个函数,以便最终用户可以在其声明中看到它支持哪些类型(用户,不应该为了获得这些知识而检查定义)。

这是我的具体示例,我使用 B. Stroustrup 在“C++ 编程语言”中提出的“概念”思想,即 constexpr 谓词,而不是 c++20 的概念,因为我的编译器没有支持它,但是:

template<typename T>
bool constexpr is_matrix() { ... }

template<size_t N, size_t M, typename T>
class Matrix {
    ...
    template<typename U>
    Matrix& operator+=(const U& v) {
        if constexpr (is_matrix<U>()) {
            // handle matrix case
        } else {
            // handle scalar case
        }
    }
    ...
}

这个例子取自我用来研究软件渲染的简单 Matrix/Vector 库。我的想法是要求类型 U 来满足我的概念(支持所有必要的操作,提供必要的类型别名),而不是要求它是我的 Matrix 类型,当检查失败时应该处理它作为标量。

那么这里可以应用哪些技术来使最终用户的代码更清晰,还有比 constexpr if 更好的方法来提供基于概念的参数多态性吗?

我能想出的解决这个问题的唯一方法是使用 enable_if,如下所示:

    ...
template<typename U, typename = 
    enable_if_t<is_convertible_v<U, T> ||
        (is_matrix<U>() && is_convertible_v<matrix_value_type_t<U>, T>)>>
Matrix& operator+=(const U& v) {
    ...

这非常冗长,不能称为漂亮,顺便说一句,我更喜欢静态断言类型应该是可转换的或者是可转换值的矩阵,而不是隐藏这个运算符。

编辑:重新考虑我的解决方案,我实际上可以在定义中使用 static_assert,但仍然在声明中提供断言标准

template<typename U, bool check = is_convertible_v<U, T> || (is_matrix<U>() && is_convertible_v<matrix_value_type_t<U>, T>)>
... {
    static_assert(check, "Type U should be either convertible to T, or being a matrix of convertible values");
}

EDIT2:可以进一步改进为更具可读性的变体:

...
template <typename U, bool check = std::disjunction_v<
    compatible_type<This, U>,
    compatible_matrix<This, U>,
    compatible_vector<This, U>>>
Matrix& operator+=(const U& v) {
    assert_compatible<check>();
...

【问题讨论】:

  • 您打算以任意尺寸进行渲染吗?软件渲染通常只需要 3x3、3x4 或 4x4 矩阵。如果您尝试使用模板和概念将其抽象为遗忘,您最终可能会得到更臃肿、更难理解的代码。
  • 不,我渲染 3D,所以主要是 4x4 矩阵,而且我的向量实际上是 3x1 和 4x1 矩阵的别名。我以前在 C++03 是最近的标准时就知道 C++,所以我同时完成了两个任务:学习软件渲染管道和现代 C++ 的特性
  • 你要求这么多痛苦!为什么不帮自己一个忙,参加 Vector 课程?
  • 不,向量概念对我来说已经足够了:D 我实际上试图获得足够的经验和“高级模板”的舒适度,所以我以后可以尽可能少地完成 Stroustrup 书中的 N 维矩阵类(对我来说) 代码重复 :)

标签: c++ templates c++-concepts


【解决方案1】:

只需为您想要支持的所有类型重载运算符即可。您还可以添加另一个生成可理解的编译时错误的版本

 Matrix& operator+=(Matrix const&);

 template<typename matrix, typename=enable_if_t<is_matrix<matrix>::value>>
 Matrix& operator+=(matrix const&);

 template<typename scalar, typename=enable_if_t<is_scalar<scalar>::value>>
 Matrix& operator+=(scalar);

 template<typename arg>
 Matrix& operator+=(arg&&)        // catch attempts to add wrong argument type
 {
     static_assert(is_matrix<arg>::value || is_scalar<arg>::value,
         "Matrix::operator+=(arg) requires matrix or scalar argument");
     assert(false);
     return*this;
 }

您也可以声明最后一个运算符[[noreturn]]

【讨论】:

  • 如果我还需要支持向量概念怎么办? is_vector&lt;vector&gt;,重载不会进一步帮助我,因为我添加了更多的概念来支持,(T const &amp;) 签名已经定义了。
  • @kravitz 您已确保重载不会在他们接受的内容中重叠。如果他们这样做了,那么在您到达 C++ 重载解决规则之前就会遇到一个问题:如果您既可以“向量添加”它又可以“矩阵添加”,那么“添加”某些东西意味着什么应该 “ 它。这可能涉及编译时间矩阵大小以消除歧义
  • 嗯,我用的向量加法其实和矩阵加法是一样的,但是转置与否都无所谓。什么具体概念都没关系,我在徘徊这个解决方案如何在N(N>2)个概念上推广
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多