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.
留言
張貼留言