Wednesday, March 19, 2014

Typelists, Ch3, Modern C++ Design

這章開頭先介紹使用 Typelist 的需要,其中一個情況是把 design pattern 套用到多個 type 的時候。這段動機不是很明確,先把這章當作練習 template 的暖身,也是很有趣味。就像 design pattern 一樣,需要的時候,就會感覺到這些小工具的好處。

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 參數。

96918602

產生出來的繼承樹裡面,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 都有兩份)

[A]^-[B], [A]^-[C], [B]^-[C], [C]-[note: C cannot cast to A directly.], [D]^-[E], [D]^-[F], [E]^-[G], [F]^-[G], [G]-[note: G cannot cast to D directly. Cast through F like (D&)(F&) is OK. ].png

改進以後,新增一個 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: