2017-09-03 65 views
1

作爲編寫性能敏感數值模擬代碼的一部分,出現了一個設計問題,我花了幾天的時間思考並找不到令人滿意的解決方案。作爲我的模擬的一部分,我有一個相當大的容器類模板,其部分特化包含與該模擬部分相關的數據成員。然而,應用於該數據的轉換可能會有很大差異,並且會通過多個不同的模板參數提供。下面是這種行爲的簡化最小的玩具的例子,我有幾點:解決部分成員專業化缺乏性能的具體設計約束

enum AlgType1 {Alg1Kind1, Alg1Kind2}; 
enum AlgType2 {Alg2Kind1, Alg2Kind2}; 
enum AlgType3 {Alg3Kind1, Alg3Kind2}; 

template<int D=2, AlgType1 T1=Alg1Kind2, AlgType2 T2=Alg2Kind1, AlgType3 T3=Alg3Kind1> 
struct Grid {}; 

template<AlgType1 T1, AlgType2 T2, AlgType3 T3> // etc. 
struct Grid<2, T1, T2, T3> { 
    const int N1, N2; 
    const SomeData d1, d2; 
    Grid(const GridParams params) : N1(params.N1), N2(params.N2), d1(params.d1), d2(d2) {} 

    template<AlgType T1> 
    SomeData AlgImplementation; 
}; 

template<AlgType1 T1, AlgType2 T2, AlgType3 T3> 
template<> 
SomeData Grid<2,T1,T2,T3>::AlgImplementation<Alg1Kind1>() { 
    return d1*N1 + d2*N2; // just do something with the data in Grid 
} 

不幸的是,這種方法不能工作,因爲即使完全明確的成員模板特不準,除非類模板也完全專業化。在我的情況下,這將意味着明確地說明我寫的所有不同算法類型的每種可能組合,所以這不是一個選項。如果我可以使用具有虛函數的常規運行時多態,問題將變得微不足道,但是性能開銷是我無法承受的(是的,我已經對它進行了基準測試,而且很重要)。到目前爲止,我嘗試過的簡單模板替代方法或者不會擴展(在代碼膨脹方面),因爲會增加更多的算法和更多類型的算法(如傳統CRTP的情況),或者帶有自己的重要問題。

迄今爲止我發現的最不好的解決方案涉及算法的基於策略的簡單設計實現,其中包含實際工作的靜態成員函數。然而,這種解耦網格類的主要範圍的算法的實現,所以必須要麼由每個變量傳遞在數據,或者可選地,使用類似的指針傳遞給相關的網格實例:

template<class Grid, AlgType1 T1> class Alg1Policy {}; 
template<class Grid> class Alg1Policy<Alg1Kind1> { 
    static SomeData run(Grid grid) { 
     return grid->d1*grid->N1 + grid->d2*grid->N2; 
    } 
} 
// and then in the partially specialized Grid class, the last two lines change to: 
SomeData AlgImplementation() { 
    return Alg1Policy<decltype(this),T1>::run(this); 
} 

無論哪種方式,我會產生相當大的語法開銷,無法完全緩解以及在後一種情況下追加一些額外的指針。因此我對此解決方案不滿意。我還考慮了一些其他選項,涉及enable_if和類似的構造,但隨着算法數量的增加,這些方法的複雜性增長得太快。請注意,儘管這裏有簡單化的示例,但我也擁有算法特化,這些特化依賴於同時具有特定值組合的兩個或更多不同模板參數。

這裏的基本問題是,我希望將數據保留在與通常從組件(政策,特徵或其他構造)組裝的行爲相同的範圍內以減少樣板並避免不必要的性能成本。在缺乏對部分成員專業化的語言支持的情況下,有沒有更好的方法我沒有想到?或者,我可能會認爲這完全是錯誤的方式?

回答

1

您可以僱用SFINAE'專門化'您的會員功能。

#include <type_traits> 

enum AlgType1 {Alg1Kind1, Alg1Kind2}; 
enum AlgType2 {Alg2Kind1, Alg2Kind2}; 
enum AlgType3 {Alg3Kind1, Alg3Kind2}; 

template<int D=2, AlgType1 T1=Alg1Kind2, AlgType2 T2=Alg2Kind1, AlgType3 T3=Alg3Kind1> 
struct Grid {}; 

template<AlgType1 T1, AlgType2 T2, AlgType3 T3> // etc. 
struct Grid<2, T1, T2, T3> { 
    const int N1, N2; 
    const SomeData D1, D2; 
    Grid(const GridParams params) : N1(params.N1), N2(params.N2), D1(params.d1), D2(params.d2) {} 

    template<AlgType1 Ta> 
    std::enable_if_t<Ta==Alg1Kind1,SomeData> AlgImplementation() 
    { return D1*N1 + D2*N2; } 

    template<AlgType1 Ta> 
    std::enable_if_t<Ta==Alg1Kind2,SomeData> AlgImplementation() 
    { return D1*N1 - D2*N2; } 
}; 

請注意,您可以申請SFINAE不同,例如在一個參數的類型

template<AlgType1 Ta> 
    SomeData AlgImplementation(std::enable_if_t<Ta==Alg1Kind2>* =nullptr); 

或第二個模板參數

template<AlgType1 Ta, typename E=std::enable_if_t<Ta==Alg1Kind2>> 
    SomeData AlgImplementation(); 

順便說一句的,你在執行什麼樣的算法? (我只是好奇)。

+0

我曾經在鏈接'std :: enable_if'('_t'版本存在之前)和生成過去脆弱的不可維護的代碼方面遇到了不好的經驗,但實際上這似乎乍一看很好。我將爲我目前更復雜的實現之一寫一個原型,除非遇到重大問題或有人提出更好的解決方案,否則明天接受此答案,謝謝!爲了回答你的問題,這是用於物理研究的更大流體 - 結構相互作用求解器的格子玻爾茲曼部分。 – user13492

+0

'std :: enable_if_t '只是'std :: enable_if_t :: type'的別名。我不太明白這是如何給你*脆弱的不可維護的代碼*以及你的意思*鏈接*'std :: enable_if'。 – Walter

+0

我的可維護性問題是在不同的背景下,在不同的項目中進行模板元編程。我解決這個問題的一個遺留問題是,我必須使用完整的模板參數列表來引用每個成員函數 - 「AlgImplementation '等。由於別名和別名模板的工作方式,我不能廢除使用簡單的'using'語句的樣板文件,即使所有的信息在編譯時和在相同的範圍內都可用。有沒有簡單的方法呢? – user13492