Defining Typelist
Typelist 是把 type 串成一個像 linked list 結構的 template,定義很簡單。struct NullType {};
template<class T, class U>
struct Typelist
{
typedef T Head;
typedef U Tail;
};
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, TYPELIST_1(T2)>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, TYPELIST_2(T2, T3)>
...
使用的時候,只要把 data type 填入 TYPELIST_XX(...) 就可以得到一個 Typelist,像 linked list 一樣一個接一個。這個定義很像 std::pair,差別是 Typelist 沒有資料。
Calculating Length
接下來都是 template 寫法的練習。任一個 Typelist T 都是個 class type,如何 compile time 計算 T 裡面放幾個 type?用 class template 定義 enum 回傳數字。template<class T>
struct Length
{
enum{ value = Length<typename T::Tail>::value + 1 };
};
template<class T>
struct Length<TYPELIST_1(T)>
{
enum{ value = 1 };
};
這是很常見的 recursive template 寫法,先定義遞迴的方式,然後給 base case。這章裡面也有提出一個論證,說明 template 只能用 recursion,不能用 iteration,因為數值不能 reassign.
Indexed Access
另外一個問題是,要如何取出 Typelist T 裡面的第 N 個 type?跟計算長度很像,也是用 recursive 寫法計算,找到以後用 typedef 傳出來。template<class T, int N> struct IndexOf;
template<class T, class U>
struct IndexOf<Typelist<T, U>, 0>
{
typedef T Result;
};
template<class T, class U, int N>
struct IndexOf<Typelist<T, U>, N>
{
typedef typename IndexOf<U, N-1>::Result Result;
};
這邊跟 Length 寫法不太一樣,差別在於 Length 有一般的形式,是靠 Typelist 的特徵 member type Tail 批配的,如果其他的 type 也有一樣的特徵,可能會被誤用。而 IndexOf 是提供的兩個 specialization,第一個參數 T 都指定是 Typelist,就算有一樣的特徵也不能用。書中的寫法是第二種,也就是較嚴格指定 Typelist 來做 specialization,當作 library 應該比較妥當。
Class Generation with Typelists
中間介紹了一些 Typelist 操作的工具,抓到 recursion 的訣竅以後都想得出來。這節要做一個新的突破,給定一個 Typelist 和一個 template class 如下,能不能有個 template class 自動幫我繼承 template class 套用 Typelist 產生的實體?TYPELIST_3(int, char, std::string)
template <class T> struct Holder{ T value; }
也就是說,要怎樣讓 GenScatterHierarchy<TYPELIST_3(int, char, std::string), Holder> 這個 class 自動多重繼承 Holder<int>, Holder<char>, Holder<string> 這三個?最簡單是直接寫個 template class 多重繼承 Holder<T1>, Holder<T2>, Holder<T3>,然後去讓 compiler 把真正的 type 填進去。但是 C++11 以前沒有變動長度的 template參數,所以每個長度都要寫一次。別忘了, template 一開始就是為了避免做一樣的事設計的。這邊也可以用。
template<class T, template<class> class BASE> struct GenScatterHierarchy;
template<class T, template<class> class BASE >
struct GenScatterHierarchy<TYPELIST_1(T), BASE> : public BASE<T>{};
template<class T, class U, template<class> class BASE >
struct GenScatterHierarchy<Typelist<T, U>, BASE> :
public BASE<T>,
public GenScatterHierarchy<U, BASE> {};
這是最直接的想法,把 Holder 這個 template class 抽換成 BASE,並且每次抽出一個 type 繼承 BASE<T>,剩下的就用 recursion 繼承。注意 BASE 的宣告方式,因為沒給定參數不是一般的 class,要告訴 compiler 這是個 template class,而且只有一個 class 參數。
產生出來的繼承樹裡面,GenScatterHierarchy 本身是沒有任何 member 的,這就是巧妙的地方,因為 runtime 不佔空間,使用上也不會跟 BASE 的任何 member 發生命名衝突。因為是多重繼承,轉形成適當的 base type 就可以取用 BASE 的任何 member.
// FieldTraits
template<class T>
struct FieldTraits;
template<class TL, template<class> class BASE>
struct FieldTraits< GenScatterHierarchy<TL, BASE> >
{
typedef TL Typelist;
template<class U>
struct Bind2
{
typedef BASE<U> Result;
};
};
// field()
// GenScatterHierarchy<TYPELIST_3(int, char, std::string), Holder> info;
// int i = field<int>(obj).value;
template<class T, class U>
typename FieldTraits<U>::template Bind2<T>::Result& field(U& rhs)
{
return rhs;
}
上面的寫法也很巧妙,先例用 template function field() 自動代換 FieldTraits<U> 得到 U = GenScatterHierarchy<TYPELIST_3, Holder>,這樣就可以在 FieldTraits 裡面處理 Typelist,也可以使用 Holder 並且產生 Holder<T> 的實體 type。這是 traits 的技巧,直接用 function 很難達到。這邊的另外一個問題是,如果 Typelist 裡面有重複的 type,這樣的 cast 會變成 ambiguous error。事實上,這個情況產生繼承樹的時候,gcc 就會 warning 同一個問題。但是,如果多重繼承的時候,在中間加一層繼承,那麼 compiler 就能區分這樣要從哪條路 cast 回去,才能區分這個名字用的是哪個實體。(下面圖裡, D 所有的 member G 都有兩份)
改進以後,新增一個 helper: template class ScatterLeftBase。注意他的第一個參是為了讓 compiler 可以區分這是兩個不同的實體 type,才把 Typelist 後面的部份一起傳進去。實際用來繼承的,只有 Typelist 第一個 type T。書裡沒有提到這部份,實作的時候要注意。
template<class T, template<class> class BASE> struct ScatterLeftBase;
template<class T, class U, template<class> class BASE>
struct ScatterLeftBase<Typelist<T, U>, BASE> : public BASE<T> {};
template<class T, template<class> class BASE> struct GenScatterHierarchy;
template<class T, template<class> class BASE >
struct GenScatterHierarchy<TYPELIST_1(T), BASE> : public ScatterLeftBase<TYPELIST_1(T), BASE>{};
template<class T, class U, template<class> class BASE >
struct GenScatterHierarchy<Typelist<T, U>, BASE> :
public ScatterLeftBase<Typelist<T, U>, BASE>,
public GenScatterHierarchy<U, BASE> {};
實作 cast 的時候,給定第 N 個 type,需要用 resursive function 一層層轉型回去,書中用 template function overload 來處理,但是也可以把 function 單獨放在 class 裡面,並且用 template class specialization 來區分使用哪個 function。用 function overload 需要一個額外的參數區別,放在 template class 裡面就是把這個額外的參數變成 template class 的參數,意思差不多,就看怎樣寫比較合理。
template<int N, class T>
struct Cast
{
template<class TL, template<class> class BASE>
static T& cast(GenScatterHierarchy<TL, BASE>& rhs)
{
return Cast<N-1, T>::cast(static_cast< GenScatterHierarchy<typename TL::Tail, BASE>& >(rhs));
}
};
template<class T>
struct Cast<0, T>
{
template<class TL, template<class> class BASE>
static T& cast(GenScatterHierarchy<TL, BASE>& rhs)
{
return static_cast< ScatterLeftBase<TL, BASE>& >(rhs);
}
};
No comments:
Post a Comment