0

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.

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

The initial matrix is 4x4 matrix of type double.


    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

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
7
  • as always with recursion, you need a base case that stops the recursion somewhere. Btw "a specialization that cannot be instantiated during runtime" ... no it all happens at compile time Commented Jan 27, 2020 at 12:13
  • The recursion never takes place in my pasted code due to if(false). I understand the difference, what I meant is that I don't understand why it's instantiated, since it can never be used during runtime. Commented Jan 27, 2020 at 12:19
  • 2
    if(false) is evaluated at runtime, but already at compile time all branches must be correct. constexpr if might help Commented Jan 27, 2020 at 12:20
  • its not just if(false), one method returns GenericMatrix<T, rows - 1, columns - 1> Commented Jan 27, 2020 at 12:21
  • It does, but it's only called in the if statement. if constexpr(false) works, but if constexpr(rows >= 2 && columns >= 2), which is the actual escape condition, does not. Commented Jan 27, 2020 at 12:23

1 Answer 1

2

As already pointed out in the comments, you have to stop the recursion by defining the specialization 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). 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>. 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. Therefore, you must provide a sepcialization template<class T> GenericMatrix<T,1,1> whose GetDeterminant does not call 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
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.