【问题标题】:Use constructor parameters outside constructor in constexpr class在 constexpr 类中使用构造函数之外的构造函数参数
【发布时间】:2021-03-18 06:34:43
【问题描述】:

我正在尝试实现类似矩阵的类,使用 std::array 来实际存储数据。所有数据在编译时都是已知的。

我希望能够使用初始化列表来初始化矩阵。类似于

Matrix m = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

我还希望能够在实例化期间设置矩阵的尺寸。

我当前的代码类似如下:

template<int m, int n>
class Matrix {
public:

    using initializer_list2d = std::initializer_list< std::initializer_list<float> >;
    using array2d = std::array< std::array<float, m>, n >;

    consteval Matrix(initializer_list2d initList) {
        // static_assert to check initList length [...]

        int i = 0;
        for (auto &row : initList) {
            // static_assert to check row length [...]

            std::copy(row.begin(), row.end(), data_[i].begin());
            i++;
        }
    }

    // Definitions of operators and methods [...]
private:
    array2d data_;
};

为了使用它,我必须通过模板设置尺寸:

Matrix<3, 3> m = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

在我看来,由于构造函数是 consteval (而且它的所有参数无论如何都必须在编译时知道),它应该以某种方式打赌可以仅通过初始化列表推断维度。

我的应用程序无法使用动态数据类型(例如向量),因为该程序必须能够在不访问堆的情况下运行。

有什么方法可以实现吗?

提前感谢您的帮助!

【问题讨论】:

  • 您的构造函数的参数不必在编译时知道。即使他们这样做了(通过consteval),你也必须处理constant evaluation happening after template instantiation
  • 你说得对,我忽略了一个事实,即“constexpr”实际上更像是对编译器的建议,如果这就是你的意思的话。我将重新表述我的部分问题。
  • 我应该说的是不要错误地引用那篇论文,即不断的评估是分开的,而不是之后发生的。无论何时发生评估(在这种情况下为构造函数调用),它都无法在该过程中实例化模板。我看不出与initializer_list 对应的技术原因仍然使构造函数成为列表构造函数,但也提供大小作为模板参数,因为编译器肯定知道它有多大。在不以某种方式妥协的情况下,我不知道该怎么做。
  • 非常感谢您的快速回复!我将尝试使用您提供的新信息找到解决方案,否则我可能只需要使用模板类
  • @zomnombom 请查看编辑后的答案。它被简化了一点。

标签: c++ c++11 constructor c++14 constexpr


【解决方案1】:

在 C++17 之前,类模板没有扣除模板参数,所以这是不可能的。

从 C++17 开始,你可以编写这个演绎指南:

template<int m, int n>
Matrix(float const (&)[n][m]) -> Matrix<m, n>;

并在构造Matrix时添加一对额外的大括号:

Matrix m = 
{
 {
  {1, 2, 3},
  {4, 5, 6},
  {7, 8, 9}
 }
};

这是demo

【讨论】:

  • 有趣的是,我没有意识到它不适用于std::array 的原始数组。在 C++17 之前,您可以使用传统的 make_ 函数,这只是意味着您需要在大括号周围额外添加一个 ()。 (Quick example)
  • @chris 是的,一个额外的工厂函数肯定会起作用。我认为OP特别想要直接推论。 std::array 也会从 C++17 中推导出大小。
  • 我没有在 GCC 和 Clang 上测试它,但也没有在 accept this test 上进行测试(如果该构造函数采用原始 const int(&amp;)[N],则不需要推导指南)。似乎原始数组是从大括号中正确触发类型推导的唯一方法,我希望我以前知道这会起作用。在这种情况下,原始数组是 initializer_list 的模板对应物,因为 std::array 没有完成这项工作。
  • @chris 仅仅因为std::array 可以推断出大小并不意味着S 可以。您是否期望它使用构造函数来推断大小?你还是需要提供一个扣分指南,然后works
  • 我期待编译器会在构造函数的参数中推断出N(它可以用图片中的S 来完成)并将其与S 本身上的N 匹配,而无需需要S 上的原始数组指南。当谈到 CTAD 隐含的内容时,我当然有学习的空间。值得注意的是,如果构造函数本身采用原始数组 ref,则不需要指南。
【解决方案2】:

我发现了另一个有趣的解决方案,使用可变参数模板:

template<int m, class T, class... U>
struct Matrix {
    Matrix(const T (&t)[m], const U (& ...u)[m]) {
        std::copy(t, t+m, arr[0]);

        int i = 0;
        (std::copy(u, u+m, arr[++i]), ...);
    }   

    T (arr[m])[sizeof...(U) + 1]; 
};

它甚至允许去掉 cigien 答案中所需的第二个大括号,并且不需要模板推导指南(尽管它仍然需要 C++17 才能自动推导 int m):

Matrix mat = {
    {1, 2, 3},
    {4, 5, 6},
    {7, 8, 9}
};

此外,编译器似乎能够对此进行相当多的优化,如here所示

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-20
    • 1970-01-01
    • 2017-04-01
    • 1970-01-01
    • 2023-04-02
    • 2012-11-10
    • 2016-07-16
    • 1970-01-01
    相关资源
    最近更新 更多