【问题标题】:C++ recursive function with template results in zero-sized array带有模板的 C++ 递归函数导致大小为零的数组
【发布时间】:2020-01-27 12:10:05
【问题描述】:

我正在一个矩阵类中实现一个行列式函数,我想通过递归地得到一个越来越小的次要矩阵来计算它。问题在于编译器最终会创建一个维度为 0 的矩阵,即使我有一个 if 语句可以确保在运行时不会发生这种情况。

对此我有两个问题:

  1. 为什么编译器会实例化在运行时无法实例化的特化? - 我觉得我错过了一些非常明显的东西。
  2. 如何更改代码以修复递归?

这是删除所有无关内容的标题。完整源代码在这里:https://github.com/DanielEverland/Artemis/blob/master/ArtemisEngineCore/Engine/Math/Matrices/GenericMatrix.h

初始矩阵是双精度类型的 4x4 矩阵。


    template<class T, unsigned int rows, unsigned int columns>
    class GenericMatrix : BaseMatrix
    {
    public:
        // Returns the determinant of the matrix
        // Requires the matrix to be square
        T GetDeterminant() const
        {
            static_assert(rows == columns, "Cannot get determinant of non-square matrix");

            T determinant{};

            // Seeing as this is always false, a minor matrix is never created, and a zero-sized array should never be created.
            if(false)
                GetMinor(0, 0).GetDeterminant(); // If I comment-out this line the build succeeds

            return determinant;
        }

        // Returns the minor of this matrix.
        // Requires the matrix to be square.
        // Will return a matrix[N - 1, N - 1] with a removed row and column.
        GenericMatrix<T, rows - 1, columns - 1> GetMinor(unsigned int rowToDelete, unsigned int columnToDelete) const
        {
            static_assert(rows == columns, "Cannot get minor of non-square matrix");
            static_assert(rows >= 2, "Cannot get minor of a matrix that has 2 rows or fewer.");
            static_assert(columns >= 2, "Cannot get minor of a matrix that has 2 column or fewer.");

            GenericMatrix<T, rows - 1, columns - 1> minor{};

            unsigned int rowIndex = 0;
            for (unsigned int i = 0; i < minor.GetRows(); i++)
            {
                if (rowIndex == rowToDelete)
                    rowIndex++;

                unsigned int columnIndex = 0;
                for (unsigned int j = 0; j < minor.GetColumns(); j++)
                {
                    if (columnIndex == columnToDelete)
                        columnIndex++;


                    minor[i][j] = values[rowIndex][columnIndex];


                    columnIndex++;
                }

                rowIndex++;
            }

            return minor;
        }

    private:
        T values[rows][columns] = { };
    };

这是构建输出。如您所知,实例化了一个维度为 0 的矩阵

1>------ Build started: Project: UnitTests, Configuration: Debug x64 ------
1>MatrixTests.cpp
1>C:\Users\Daniel\source\repos\Artemis\ArtemisEngineCore\Engine\Math\Matrices\GenericMatrix.h(234,1): error C2087: 'values': missing subscript
1>C:\Users\Daniel\source\repos\Artemis\ArtemisEngineCore\Engine\Math\Matrices\GenericMatrix.h(200): message : see reference to class template instantiation 'ArtemisEngine::Math::Matrices::GenericMatrix<T,0,0>' being compiled
1>        with
1>        [
1>            T=Math::Matrices::T
1>        ]
1>C:\Users\Daniel\source\repos\Artemis\ArtemisEngineCore\Engine\Math\Matrices\GenericMatrix.h(194): message : while compiling class template member function 'T ArtemisEngine::Math::Matrices::GenericMatrix<T,1,1>::GetDeterminant(void) const'
1>        with
1>        [
1>            T=Math::Matrices::T
1>        ]
1>C:\Users\Daniel\source\repos\Artemis\ArtemisEngineCore\Engine\Math\Matrices\GenericMatrix.h(200): message : see reference to function template instantiation 'T ArtemisEngine::Math::Matrices::GenericMatrix<T,1,1>::GetDeterminant(void) const' being compiled
1>        with
1>        [
1>            T=Math::Matrices::T
1>        ]
1>C:\Users\Daniel\source\repos\Artemis\UnitTests\Math\MatrixTests.cpp(375): message : see reference to class template instantiation 'ArtemisEngine::Math::Matrices::GenericMatrix<Math::Matrices::T,1,1>' being compiled
1>C:\Users\Daniel\source\repos\Artemis\UnitTests\Math\MatrixTests.cpp(57): message : see reference to class template instantiation 'ArtemisEngine::Math::Matrices::GenericMatrix<Math::Matrices::T,4,4>' being compiled
1>C:\Users\Daniel\source\repos\Artemis\ArtemisEngineCore\Engine\Math\Matrices\GenericMatrix.h(234,1): warning C4200: nonstandard extension used: zero-sized array in struct/union
1>C:\Users\Daniel\source\repos\Artemis\ArtemisEngineCore\Engine\Math\Matrices\GenericMatrix.h(234,1): message : This member will be ignored by a defaulted constructor or copy/move assignment operator
1>Done building project "UnitTests.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 2 up-to-date, 0 skipped ==========

Build time 00:00:00.726
Build ended at 27/01/2020 12.56.52

【问题讨论】:

  • 与递归一样,您需要一个在某处停止递归的基本情况。顺便说一句,“在运行时无法实例化的特化”......不,这一切都发生在编译时
  • 由于 if(false),在我粘贴的代码中永远不会发生递归。我理解其中的区别,我的意思是我不明白为什么要实例化它,因为它永远不能在运行时使用。
  • if(false) 在运行时评估,但在编译时所有分支都必须正确。 constexpr if 可能会有所帮助
  • 它不仅仅是if(false),一种方法返回GenericMatrix&lt;T, rows - 1, columns - 1&gt;
  • 可以,但只在 if 语句中调用。 if constexpr(false) 有效,但实际转义条件 if constexpr(rows &gt;= 2 &amp;&amp; columns &gt;= 2) 无效。

标签: c++ recursion matrix


【解决方案1】:

正如 cmets 中已经指出的,您必须通过定义特化 GenericMatrix 来停止递归。 if(false) 在运行时进行评估(正如在 cmets 中已经指出的那样,c++17 功能 if consexpr 也可以在这里使用,但是以下答案不是基于该功能,因为问题没有标记为 c+ +17)。这直接回答了你的第一个问题。请参阅以下示例:

GenericMatrix<double,3,3> mat;
auto det = mat.GetDeterminant();

mat.GetDeterminant() 内部调用 GetMinor 并返回 GenericMatrix&lt;double,2,2&gt;。现在返回的GenericMatrix&lt;double,2,2&gt;-object 本身调用GetDeterminant,一切从头开始,因为if(false) 不会停止编译时递归。因此,您必须提供一个特殊化template&lt;class T&gt; GenericMatrix&lt;T,1,1&gt;,其GetDeterminant 不调用GetMinor

关于第二个问题,这里有一个简化的例子:

  #include <iostream> 

   template<class T, unsigned int rows, unsigned int columns>
    class GenericMatrix 
    {
    public:
        // Returns the determinant of the matrix
        // Requires the matrix to be square
        T GetDeterminant() const
        {
            T determinant = 0.;
            std::cout << "call GenericMatrix<T," << rows <<","
                      << columns <<">::GetDeterminant" << std::endl;
            auto det_minor = this->GetMinor(0, 0).GetDeterminant(); 
            // do semething with det_minor
            return determinant;
        }

    private:
        // Returns the minor of this matrix.
        // Requires the matrix to be square.
        // Will return a matrix[N - 1, N - 1] with a removed row and column.
        GenericMatrix<T, rows - 1, columns - 1> 
        GetMinor(unsigned int rowToDelete, unsigned int columnToDelete) const
        {
            GenericMatrix<T, rows - 1, columns - 1> minor{};
            std::cout << "call GenericMatrix<T," 
                      << rows <<","<< columns <<">::GetMinor with return type "
                      << "GenericMatrix<T," << rows-1 <<","
                      << columns-1 <<">::GetDeterminant" 
                      << std::endl;
            return minor;
        }

        T values[rows][columns] = { };
    };


    template<class T>
    class GenericMatrix<T,1,1> 
    {
    public:
        // Returns the determinant of the matrix
        T GetDeterminant() const
        {
            T determinant = 0.;
            std::cout << "call GenericMatrix<T,1,1>::GetDeterminant" << std::endl;
            return determinant;
        }

    private:
        T value = { };
    };


    int main()
    {
        GenericMatrix<double,4,4> mat;

        std::cout << mat.GetDeterminant() << std::endl;
        return 0;
    }

这是输出的compiled code

call GenericMatrix<T,4,4>::GetDeterminant
call GenericMatrix<T,4,4>::GetMinor with return type GenericMatrix<T,3,3>::GetDeterminant
call GenericMatrix<T,3,3>::GetDeterminant
call GenericMatrix<T,3,3>::GetMinor with return type GenericMatrix<T,2,2>::GetDeterminant
call GenericMatrix<T,2,2>::GetDeterminant
call GenericMatrix<T,2,2>::GetMinor with return type GenericMatrix<T,1,1>::GetDeterminant
call GenericMatrix<T,1,1>::GetDeterminant
0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多