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);
    }
};

Monday, March 10, 2014

Techniques, Ch 2, Modern C++ Design

這章介紹基本的 template 技巧,雖然說基本,但是並不是很直接可以想出來的。

Compile Time Assertions

即使不用 template,static/compile time assert 也是 C++ 很常用的工具,但是 standard library 沒有提供。原理是用 template specialization,提供 true 的實作,但是不提供 false 的實作。

template<bool> struct StaticAssertFail;
template<>     struct StaticAssertFail<true>{};
#define STATIC_ASSERT(value)  StaticAssertFail<value>()

Partial Template Specialization

Class template 可以只給定部份的參數特製化,但是 function template 不能,這直接看語法就懂了。也是很常用的功能。

Local Classes

C++ 允許在 function 定義裡面宣告 local class,就像 local variable 一樣。妙的是,local class 也可以繼承外面的 class,還可以把指標傳出去,就會變成外面無法看的 type。不過 local class 不能作為外面的 template 參數。

Int2Type and Type2Type

用 template 製造新的 type,可以作為 function overloading 區分用,因為製造出來的 type 不像原本的 type 會自動轉換。

template<int N>
struct Int2Type
{
    enum{ value = N };
};

template<class T>
struct Type2Type
{
    typedef T Type;
};

Type Selection

用 partial template specialization 實作下面的 template class,使得 IS_T == true 的時候,得到type T,否則得到 type U。

template
struct Select
{
    typedef T Type;
};

Detecting Convertibility and Inheritance at Compile Time

這是這章裡面最巧妙的部份,使用許多技巧偵測兩個 type 是否可自動轉型,這分成幾個步驟。

  1. 定義 class template 界面,規定 T 可以轉型成為 U 為 true
  2. 宣告兩個overload function Test 接受 T 為參數,如果 T 可以自動轉型成為 U,compiler 會選擇第一個 Test()。如果不行,就要選擇第二個 Test()
  3. 第一個 Test 直接接受參數 U,可自動轉型的情況
  4. 第二個 Test 接受其他的狀況,而且不能包含第一個 Test 的狀況。這邊比較妙,使用 Test(...) 因為 priority 低,又可以接受所有 type.
  5. 兩個 Test() 回傳不同 type
  6. 使用 sizeof() 是 compile time 就可以決定的特性,判斷 sizeof( Test( T() ) ) 的結果。為了用這個特定,兩個 Test() 回傳的 type 必須有不同的 sizeof().
  7. 因為 sizeof() 裡面不會真正執行,所以兩個 Test() 只需要宣告,不需要真正實作。但 enum exists 是 const expression,所以所有 function 都要是 static.

template <class T, class U>
struct Conversion
{
private:
    typedef char                   Small;
    typedef struct{ char c[2]; }   Big;
    static T _makeT();
    static Small _test(U);
    static Big _test(...);

public:
    enum
    {
        exists = (sizeof(_test(_makeT())) == sizeof(Small))
    };
};

Type Traits

提供 template 在 compile time 偵測 type 的資訊,包括是否為 pointer,是否為 fundamental type,最適合用來傳參數的 type 等。這裡面大部分都是用 template 列舉,就比較可以想得出來。

Monday, March 03, 2014

Policy-Based Class Design, Ch 1, Modern C++ Design

這章介紹的重點,是使用 template 參數作為 class 的行為或者結構,使得 library class 的界面可以清楚分割,並且增進執行效率。原本的範例是 smart pointer,但是其實這邊的觀念和使用 virtual function 很像。比如一組 file I/O 的 library,要設計成可以操作 local file, stdin, stdout,也可以操作網路上的檔案,會把 bool write(...) 設計成這樣:

template<class F>
class Backup : public F
{
    bool write(void* pData, size_t n)
    {
        return F().write(pData, n);
    }
};

struct LocalFile
{
    bool write(pData, n){ return fwrite(pData, 1, n, pFile);
};

struct RemoteFile
{
    ...
};

使用的時候,可以選擇不同形式的 policy 宣告。

typedef Backup<LocalFile> LocalBackup;
LocalBackup backup;
backup.write(...); 

也可以在 library 外面自訂 policy,產生新的組合:
Backup<SocialNetworkProfile> facebookBackup;

實作的困難點在於分割出要執行的 policy,還有制定 policy 的行為。上面的例子裡,template<class F> class Backup : public F 的繼承關係,可以讓 Backup 繼承 F 提供的功能,增加使用的彈性。