2017-08-28 21 views
1

我有一個模板函數定義爲:嵌套模板專業化是如何做C++

template<typename TObject> TObject Deserialize(long version, const Value &value) 

什麼,我需要做的,是寫一個專業化這將需要矢量定義爲:

template<typename TNum, int cnt> class Vec 

並仍然可以訪問cntTNum

我已經試過unsuccesfully

template<typename TNum, int cnt> Vec<TNum, cnt> Deserialize<Vec<TNum, cnt>>(long version, Value &value) 

導致錯誤:非法使用顯式模板參數

什麼是做了正確的方法是什麼?

+3

你不能部分專門化功能。因此,您必須將實際的實現隱藏到虛擬模板結構的靜態方法中,並專門化整個結構。 – HolyBlackCat

+0

只有一個載體或任何容器? – rustyx

+0

在我的情況下,Vec是外部庫的數學向量。 –

回答

4

通常,處理函數模板和需要部分專門化它們的正確答案是簡單地重載它們。在這種情況下,這個技巧並不直接工作,因爲沒有依賴於模板參數的參數,即模板參數是明確指定的而不是推導的。但是,您可以轉發到實現函數,並通過使用簡單的標記結構來重載工作。

#include <functional> 
#include <iostream> 
#include <type_traits> 
#include <vector> 
#include <array> 

template <class T> 
struct tag{}; 

template<typename TObject> 
TObject Deserialize_impl(long version, tag<TObject>) { 
    std::cerr << "generic\n"; 
    return {}; 
} 

template<typename T, std::size_t N> 
std::array<T,N> Deserialize_impl(long version, tag<std::array<T,N>>) { 
    std::cerr << "special\n"; 
    return {}; 
} 

template<typename TObject> 
TObject Deserialize(long version) { 
    return Deserialize_impl(version, tag<TObject>{}); 
} 


int main() { 
    Deserialize<int>(0); 
    Deserialize<std::array<int,3>>(0); 

    return 0; 
} 

活生生的例子:http://coliru.stacked-crooked.com/a/9c4fa84d2686997a

總的來說,我發現因爲有您可以利用與功能的許多事情,這些方法強烈最好用靜態方法(這裏的其他主要方法)struct的偏特,與專業化相比,它的行爲更直觀。因人而異。

+1

我甚至會將這個impl命名爲相同的名稱:不同的arg計數使得它非常安全,並且它使得ADL擴展更加難看。 – Yakk

+0

@Yakk一個公平的點;我想我會留下答案,因爲對於那些對重載稍微不熟悉的人來說,最大限度地保持清晰度(所以他們不會把它誤認爲遞歸),但我同意你的看法。 –

0

雖然功能性標籤發送是一個不錯的方法,但是這裏有一個用於比較的類專用化版本。兩者都有它們的用途,我認爲這兩者都不是一個固有的令人遺憾的決定,但也許更符合你的個人風格。 對於你寫一個需要自定義的反序列化處理任何一類,只寫解串器類的專業化:

#include <iostream> 
#include <string> 
using namespace std; 

using Value = std::string; 

// default deserialize function 
template <typename TObject> 
struct Deserializer { 
    static TObject deserialize(long version, const Value &value) { 
     std::cout << "default impl\n"; 
     return TObject(); 
    } 
}; 

// free standing function (if you want it) to forward into the classes 
template <typename TObject> 
TObject deserialize(long version, const Value &value) { 
    return Deserializer<TObject>::deserialize(version, value); 
} 

// Stub example for your Vec class 
template<typename TNum, int cnt> class Vec { }; 

// Stub example for your Vec deserializer specialization 
template <typename TNum, int cnt> struct Deserializer<Vec<TNum, cnt>> { 
    static auto deserialize(long version, const Value &value) { 
     std::cout << "specialization impl: cnt=" << cnt << "\n"; 
     return Vec<TNum, cnt>(); 
    } 
}; 

int main() { 
    Value value{"abcdefg"}; 
    long version = 1; 

    deserialize<int>(version, value); 
    deserialize<Vec<int, 10>>(version, value); 
} 
+0

這種方法確實有明顯的缺點,儘管在這種情況下,只返回有問題的類型並且不接受它,但不太明顯。其中之一是它對於多態類型的作用要差得多。專業化與繼承不能很好地結合。函數也可以讓你免費轉換爲const;有時甚至專業化,你甚至可以被咬,因爲'T'和'const T'是不同的類型! –

+0

@NirFriedman這些都很好,謝謝。 :)但是我沒有看到Tag Tag 和Tag 的標籤發送的工作方式不同,因爲Tag類實例化沒有多態關係。 –

+0

就像我說過的,你不會在這裏看到它,因爲你正在返回不同的類型,無論如何都不能是多態的。但是,如果您是通過引用接受該類型,則可以免費獲得該轉換(以及常規資格)。要記住的主要原因是重載重複專業化是微不足道的,我的答案表明。但是,重載可以讓你獲得許多有用的技巧,這些技巧可以通過專業化複製更多的工作。 –

0

理想的情況下在這種情況下,Vec要體現自己的模板參數作爲成員Vec::value_typeVec::size()這應該是constexpr

如果該類未能在其自己的接口中提供自己的屬性,那麼接下來最好的方法就是定義自己的擴展接口。在這種情況下,你可以有單獨的元函數(比如訪問函數)或者特性類(比如輔助視圖類)。我更喜歡後者:

template<typename> 
struct vector_traits; 

template< typename TNum, int cnt > 
struct vector_traits< Vec< TNum, cnt > > { 
    typedef TNum value_type; 
    constexpr static int size = cnt; 
}; 

template<typename TVec> TVec Deserialize(long version, Value &value) { 
    typedef vector_traits<TVec> traits; 
    typedef typename traits::value_type TNum; 
    constexpr static int cnt = traits::size; 
    … 
} 

該解決方案適合任何現有的功能,甚至使簽名更清潔。此外,該功能更加靈活,因爲您可以通過增加traits專業化而不是全新的重載來調整它。

+0

這種方法假定反序列化只採用矢量類型進行反序列化。如果有一個不相關的Foo類需要反序列化呢?專門vector_traits,所以它會編譯? –

+0

很明顯,函數體只能用於概念向量的類型。禁用其他類型的函數簽名(通過SFINAE或Concepts)並提供更多的重載。 – Potatoswatter