简体   繁体   English

带有模板的 C++ 递归函数导致大小为零的数组

[英]C++ recursive function with template results in zero-sized array

I'm implementing a determinant function in a matrix class, which I'd like to calculate by recursively getting a smaller and smaller minor matrix.我在矩阵类中实现了一个行列式函数,我想通过递归地得到一个越来越小的次矩阵来计算它。 The problem lies with the compiler eventually creating a matrix with a dimension of 0, even though I have an if-statement that ensures this doesn't happen in runtime.问题在于编译器最终创建了一个维度为 0 的矩阵,即使我有一个 if 语句来确保这不会在运行时发生。

I have two questions regarding this:关于这个我有两个问题:

  1. Why is the compiler instantiating a specialization that cannot be instantiated during runtime?为什么编译器实例化一个在运行时无法实例化的特化? - I get the feeling that I'm missing something that's very obvious. - 我觉得我错过了一些非常明显的东西。
  2. How do I change my code in order to fix the recursion?如何更改我的代码以修复递归?

Here's the header with everything non-related removed.这是标题,删除了所有不相关的内容。 Full source code here: https://github.com/DanielEverland/Artemis/blob/master/ArtemisEngineCore/Engine/Math/Matrices/GenericMatrix.h完整源代码在这里: https : //github.com/DanielEverland/Artemis/blob/master/ArtemisEngineCore/Engine/Math/Matrices/GenericMatrix.h

The initial matrix is 4x4 matrix of type double.初始矩阵是 double 类型的 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] = { };
    };

Here's the build output.这是构建输出。 As you can tell, a matrix with a dimension of 0 is instantiated如您所见,一个维度为 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

As already pointed out in the comments, you have to stop the recursion by defining the specialization GenericMatrix .正如评论中已经指出的那样,您必须通过定义专业化 GenericMatrix 来停止递归。 The if(false) is evaluated at runtime (as already pointed out in the comments the c++17 feature if consexpr can be used here as well, however the following answer is not based on that feature since the question is not labelled c++17). if(false)在运行时进行评估(如评论中已经指出的 c++17 特性if consexpr也可以在此处使用,但是以下答案并非基于该特性,因为该问题未标记为 c+ +17)。 This directly answers your first question.这直接回答了你的第一个问题。 See the following example:请参阅以下示例:

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

mat.GetDeterminant() internally calls GetMinor that returns GenericMatrix<double,2,2> . mat.GetDeterminant()内部调用GetMinor返回GenericMatrix<double,2,2> Now the returned GenericMatrix<double,2,2> -object itself calls GetDeterminant and everything starts from the beginning, since if(false) does not stop the compile time recursion.现在返回的GenericMatrix<double,2,2> GetDeterminant本身调用GetDeterminant并且一切从头开始,因为if(false)不会停止编译时递归。 Therefore, you must provide a sepcialization template<class T> GenericMatrix<T,1,1> whose GetDeterminant does not call GetMinor因此,您必须提供一个特殊化template<class T> GenericMatrix<T,1,1>GetDeterminant不会调用GetMinor

Regarding the second question,here's a simplified example:关于第二个问题,这里有一个简化的例子:

  #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;
    }

Here is the compiled code which outputs这是输出的编译代码

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

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

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