2012-12-11 144 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); 
}