views:

39

answers:

2

Basically, I have a matrix class like this (with a lot of operator overloads and other functions removed):

template
<
    uint32 TRows,
    uint32 TCols
>
struct Matrix
{
    float values[TRows][TCols];

    inline explicit Matrix()
    {
    }

    inline Matrix<TRows - 1, TCols - 1> minor(const uint32 col, const uint32 row)
    {
        Matrix<TRows - 1, TCols - 1> matrix;
        for(int i = 0; i < TRows; ++i)
            for(int j = 0; j < TCols; ++j)
            {
                if(i == col || j == row) continue;
                matrix.values[i - (i > col)][j - (j > row)] = this->values[i][j];
            }
        return matrix;
    }

    inline float determinant()
    {
        if(TRows != TCols) throw DimensionError("Matrix is not square");

        float det = 0;

        if(TRows <= 0)
            det = 0;
        else if(TRows == 1)
            det = this->values[0][0];
        else if(TRows == 2)
            det = this->values[0][0] * this->values[1][1] - this->values[1][0] * this->values[0][1];
        else
            for(int j = 0; j < TCols; ++j)
                det += (j % 2 ? -1 : 1) * this->values[0][j] * this->minor(0, j).determinant();

        return det;
    }
}

I don't understand why, for the line det += (j % 2 ? -1 : 1) * this->values[0][j] * this->minor(0, j).determinant();, GCC attempts to generate an immense amount of functions:

matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -49u, unsigned int TCols = -49u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -48u, unsigned int TCols = -48u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -47u, unsigned int TCols = -47u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -46u, unsigned int TCols = -46u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -45u, unsigned int TCols = -45u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -44u, unsigned int TCols = -44u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -43u, unsigned int TCols = -43u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -42u, unsigned int TCols = -42u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -41u, unsigned int TCols = -41u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -40u, unsigned int TCols = -40u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -39u, unsigned int TCols = -39u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -38u, unsigned int TCols = -38u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -37u, unsigned int TCols = -37u]'
matrix.cpp:95:   instantiated from `float Matrix<TRows, TCols>::determinant() [with unsigned int TRows = -36u, unsigned int TCols = -36u]'

This is probably some error in my code, but I can't for the life of me see where I'm going wrong. Help would be much appreciated!

+4  A: 

Matrix::minor(0, j) returns a matrix of size (N-1, N-1). Calling for its determinant makes the process recursive, which means you are generating a minor (and determinant method) for all p integers, starting from N down to ... -infinity ?

Have you added a specialization for the case where N==1 or N==0 ? You have to make sure recursion stops !

Try adding something like :

template<>
struct Matrix<1,1>
{
  float values[1][1];

  float determinant(){ return values[0][0]; }
};

EDIT : Recursion has to be stopped at compile time. Adding an if statement inside the method does not prevent the compiler from compiling all possible paths.

Benoît
Thanks! It works! Just a small typo in your code though -- `int float` should simply be `float` :)
rfw
@rfw: That's a dynamic-time branch, you need a static-time branch. Add a specialization for a 0 sized matrix.
GMan
A: 

I agree with "Benoît"

The even fact that during the runtime you never call minor function on an 'empty' matrix - doesn't prevent the compiler from generating that code.

To avoid this you should play with explicit template specializations. So that even at compile time the compiler won't encounter program paths that lead to infinite template function generation.

Like this one:

template <int N>
float determinantInternal()
{
    // Standard algoritm for NxN matrix
    float det = 0;
    for(int j = 0; j < TCols; ++j)
        det += (j % 2 ? -1 : 1) * this->values[0][j] * this->minor(0, j).determinant();

    return det;
}

template <>
float determinantInternal<0>()
{
    return 0;
}

float determinantInternal<1>()
{
    return this->values[0][0];
}

float determinantInternal<2>()
{
    return this->values[0][0] * this->values[1][1] - this->values[1][0] * this->values[0][1];
}

template <>
float determinant()
{
    if (TRows != TCols)
        throw DimensionError("Matrix is not square");
    // Alternatively you can use something to prevent this from compiling. 
    // This way you produce the compile-time error if attempted to call
    // determinant on a non-square matrix
    BOOST_STATIC_ASSERT(TRows == TCols);

    return determinantInternal<TRows>();
}

One more advantage of this type of specialization is that you handle different matrix types (empty, non-square, 1x1, NxN) at compile time. Hence you get a bit faster code.

Plus you can prevent calling determinant on non-square matrixes. In such a case you'll get a compiler/linker error when attempting to do it, rather than exception at run-time.

valdo