2012-12-11 503 views
3

我正在写一个模板数学矩阵类来适应一些新的C++ 11点的特性,基本宣告如下:调用模板函数递归(C++)

template <typename Type, int kNumRows, int kNumCols> 
class Matrix { ... }; 

类有一个成员函数返回一个未成年人(后来用于计算N×N矩阵的行列式)。

Matrix<Type, kNumRows - 1, kNumCols - 1> minor(const int row, const int col) { 
    static_assert(kNumRows > 2, ""); 
    static_assert(kNumCols > 2, ""); 

    ... 
} 

我然后创建了一个非成员函数来计算任何正方形矩阵的行列式:)

template <typename Type, int kSize> 
Type determinant(const Matrix<Type, kSize, kSize>& matrix) { 
    switch (kSize) { 
    case 2: 
    return 0; // For now unimportant 
    case 3: 
    // Recursively call the determinant function on a minor matrix 
    return determinant(matrix.minor(0, 0)); 
    } 
    ... 
} 

在主(I创建一个3×3矩阵并在其上调用determinant这不会编译。编译器有效地移动到案例3,创建一个次矩阵并在其上调用determinant然后它再次步入case 3,通过尝试创建1x1次要结果产生static_assert。

问题很简单:我在这里错过了什么吗?是否像递归调用模板函数简单地不允许?这是一个编译器故障(我怀疑它)?

为了完整起见,我使用了Clang ++。

+0

只是一个小的设计评论 - 你真的不应该使用尺寸或索引“INT”;另一个需要避免的是常量传值;请参阅http://www.viva64.com/en/a/0050/和http://www.viva64.com/en/t/0030/和http://www.devx.com/tips/Tip/26546 – Matt

+0

@Matt:感谢提醒,我查看了链接。切换到size_t/ptrdiff_t。按常值传递确实看起来有点偏离,但Herb Sutter(链接指向的人)自己说'仍然使参数在同一个函数的定义中为常量,如果它不会被修改'。当我使用模板时,声明和定义在同一个文件中,这使得剪切结点变得有点困难。 –

+0

当然,没问题!模板的含义好点,够公平的。 – Matt

回答

3

模板确定要在编译的时候做的,但switch声明确定如何在运行时做的。即使正确的情况在编译时“明显”,编译器也会为所有开关情况生成代码,或者至少验证有效性。

而不是使用switch,尽量超载决定:

template <typename Type> 
Type determinant(const Matrix<Type, 1, 1>& matrix) { 
    return matrix(0,0); 
} 

template <typename Type> 
Type determinant(const Matrix<Type, 2, 2>& matrix) { 
    return 0; // (incorrect math) 
} 

template <typename Type, int kSize> 
Type determinant(const Matrix<Type, kSize, kSize>& matrix) { 
    return determinant(matrix.minor(0,0)); // (incorrect math) 
} 
+0

接受此答案,因为它解释了代码中最清晰的问题(这将帮助具有相同问题的人)。谢谢!为了将来引用其他人:这个问题的数学确实是不正确的,还有其他资源要学习矩阵数学。 –

3

编译器会生成所有代码路径,即使这些代码路径在执行过程中并未全部访问(实际上可能会在优化步骤中被删除)。因此,determinant<Type, kSize - 1, kSize - 1>总是实例化,即使是kSize < 3.

您需要部分专业的功能,以防止这一点,你需要适当超载determinant功能:

template <typename Type> 
Type determinant(const Matrix<Type, 2, 2>& matrix) { 
    ... 
} 

这使得switch顺便说一句,在你的功能冗余的声明。

+0

我不知道编译器是否创建了所有的代码路径,但是如果您考虑这一点,这是有道理的。模板和开关的组合使用看起来确实不错,但我无法理解我做错了什么。感谢您清除此:) :) –

1

你需要使用模板特在编译的时候做开关:

template <typename Type, int kSize> 
struct Determinate { 
    Type operator()(const Matrix<Type, kSize, kSize>& matrix) const { 
     // Recursively call the determinant function on a minor matrix 
     return Determinate<Type, kSize-1>{}(matrix.minor(0, 0)); 
    } 
}; 
template <typename Type> 
struct Determinate<Type, 2> { 
    Type operator()(const Matrix<Type, kSize, kSize>& matrix) const { 
     return 0; // For now unimportant 
    } 
}; 
template <typename Type, int kSize> 
Type determinant(const Matrix<Type, kSize, kSize>& matrix) { 
    return Determinate<Type, kSize>{}(matrix); 
}