跳到主要內容

C++: 延遲函式實例化與 CRTP

C++: 延遲函式實例化與 CRTP

Curiously recurring template pattern (CRTP) 是很有趣的模式,在標準函式庫中也有像 enable_shared_from_this 等應用,各種應用情境請參照前面的 Wiki 頁面。下面寫一個簡化的例子:

template<typename T>
struct base {
  auto interface() {
      return static_cast<T*>(this)->foo();
  }
};
struct derived : base<derived> {
  int foo() { return 123; }
};
int main() {
  // your code goes here
  derived().interface();
  return 0;
}

這個範例中的 auto interface() 寫法,在 C++14 後才支援,在 C++11 以前,編譯器無法自動推導出 int 這個回傳型別而會吐出以下錯誤:

error: 'interface' function uses 'auto' type specifier without trailing return type
     auto interface() {
                    ^

也就是我們得自己寫出 auto interface() -> XXX {} ;那要 XXX 要怎麼推導呢?
直覺的寫法:

template<typename T>
struct base {
    auto interface() -> decltype(static_cast<T*>(nullptr)->foo()) {
        return static_cast<T*>(this)->foo();
    }
};

利用 decltype 取得 T::foo() (也就是 derived::foo()) 的回傳型別;不過會看到下面的錯誤訊息:

In instantiation of 'struct base<derived>':
required from here
error: invalid use of incomplete type 'struct derived'
auto interface() -> decltype(static_cast<T*>(nullptr)->foo()) {
                                ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
note: forward declaration of 'struct derived'
struct derived : base<derived> {
       ^~~~~~~

原因是,當 T 是唯一的 相依名稱 (dependent name) ,實例化流程是

1. struct derived  // 僅有型別名稱,還未定義
2. struct base<derived>
3. base<derived>::interface() {}

在第三步的時候,derived 的定義還不完整 (incomplete type) ,而造成上面的錯誤。

因此我們需要的是 延遲 base<derived>::interface 的實例化,延到甚麼時候呢?直到它被呼叫的時候,也就是 derived().interface() 這一行。而延遲實例化的手法就是把這個函式樣版化 (增加相依名稱):

template<typename T>
struct base {
  template<typename U = T>
  auto interface() -> decltype(static_cast<U*>(nullptr)->foo()) {
      return static_cast<U*>(this)->foo();
  }
};

如此一來,template<typename U> base<derived>::interface 的實例化就被延後到 derived 已經是具體型別之後,也就能正確推導出 foo 的回傳型別。有趣的是即使有預設樣版參數,仍不影響這個延遲。而且,這個技巧 C++98 也適用。

這樣看下來 C++14 其實完備了許多,寫個 auto 就大功告成啦!


Written with StackEdit.

留言

這個網誌中的熱門文章

得利油漆色卡編碼方式

得利油漆色卡編碼方式 類似 Munsell 色彩系統 ,編碼方式為 HUE LRV/CHROMA 例如 10GY 61/449 ( 色卡 ) 編碼數值 描述 10GY hue ,色輪上從 Y(ellow) 到 G(reen) 區分為 0 ~ 99 ,數值越小越靠近 Y,越大越靠近 G 61 LRV (Light Reflectance Value) 塗料反射光源的比率,數值從 0% ~ 100% ,越高越亮,反之越暗,也可理解為明度 449 chroma 可理解為彩度,數值沒有上限,越高顏色純度 (濃度) 越高 取決於測量儀器,對應至 RGB 並不保證視覺感受相同。 參考資料: 色卡對照網站 e-paint.co.uk Written with StackEdit .

C++17 新功能 try_emplace

C++17 新功能 try_emplace 回顧 emplace 大家的好朋友 Standard Template Library (STL) 容器提供如 push_back , insert 等介面,讓我們塞東西進去; C++11 之後,新增了 emplace 系列的介面,如 std::vector::emplace_back , std::map::emplace 等,差異在於 emplace 是在容器內 in-place 直接建構新元素,而不像 push_back 在傳遞參數前建構,下面用實例來說明: struct Value { // ctor1 Value ( int size ) : array ( new char [ size ] ) , size ( size ) { printf ( "ctor1: %d\n" , size ) ; } // ctor2 Value ( const Value & v ) : array ( new char [ v . size ] ) , size ( v . size ) { printf ( "ctor2: %d\n" , size ) ; memcpy ( array . get ( ) , v . array . get ( ) , size ) ; } private : std :: unique_ptr < char [ ] > array ; int size = 0 ; } ; struct Value 定義了自訂建構子 (ctor1),以指定大小 size 配置陣列,複製建構子 (ctor2) 則會配置與來源相同大小及內容的陣列,為了方便觀察加了一些 printf 。當我們如下使用 std::vector::push_back 時 std :: vector < Value > v ; v . push_back ( Value ( 2048 ) ) ; 首先 Value 會先呼叫 ctor1,傳給 push_ba...

Notes for C/C++ Programming

Notes for C/C++ Programming Notes for C/C++ Programming Off by One Error Prone # define LEN 128 char buf [ LEN ] = { 0 } ; // -1 for prevent missing NULL terminator // since strncpy doesn't preserve it for us. strncpy ( buf , something , sizeof ( buf ) - 1 ) ; // snprintf does preserve NULL terminator for us // i.e. actual size for placing characters is sizeof(buf) - 1 // but it takes extra time to parsing format snprintf ( buf , sizeof ( buf ) , something ) ; Bad things often happen when we mess with -1 or +1 to pointers point to continuous elements. Absorber # define LEN 128 char buf [ LEN + 1 ] = { 0 } ; // ^^^^ Always preserve 1 byte strncpy ( buf , somethin , sizeof ( buf ) ) ; Constant Correctness (C/C++) Bad void do_something ( char * dest , char * src ) ; struct MyList { void do_something ( std :: string & name ) ; } ; Reason : Will the name be modified by the lookup method? Will stat...