Template Metaprogramming
生活随笔
收集整理的這篇文章主要介紹了
Template Metaprogramming
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Template Metaprogramming
1. 何謂 Metaprogramming?
2. Metaprogramming 的幾種途徑
第二種么,Preprocessor 當然算是了,template 則是重頭戲了。
3. 舉幾個簡單的例子先?
CppTM 領域最經典最簡單的例子莫過于計算階乘了,它簡單而有用,同時體現了 CppTM 的遞歸本質。我想絕大多數人當年學習遞歸的時候也是從這個例子開始的:
#include <iostream>
using namespace std;
template <int N>
struct factorial
{
??? static const int value = N * factorial<N - 1>::value;
};
template<>
struct factorial<0>
{
??? static const int value = 1;
};
int main()
{
??? cout << "factorial<10>: " << factorial<10>::value << endl;
??? cout << "sizeof char[factorial<4>::value]: " <<
??????????? sizeof(char[factorial<4>::value]) / sizeof(char) << endl;
}
輸出:
factorial<10>: 3628800
sizeof char[factorial<4>::value]: 24
這只是一個回顧,上面的簡單程序就不用我解釋了吧?下面的這個 remove_cv 算法會去掉參數類型的 const 和 volatile 修飾符(如果有的話),Boost type_traits 就是這么干的。
#include <iostream>
using namespace std;
template <class T> struct remove_cv
{ typedef T type; };
template <class T> struct remove_cv<const volatile T>
{ typedef T type; };
template <class T> struct remove_cv<const T>
{ typedef T type; };
template <class T> struct remove_cv<volatile T>
{ typedef T type; };
int main()
{
??? cout << "remove_cv<const int>: "
???????? << typeid(remove_cv<const int>::type).name() << endl;
??? cout << "remove_cv<volatile int>: "
???????? << typeid(remove_cv<volatile int>::type).name() << endl;
??? cout << "remove_cv<const volatile int>: "
???????? << typeid(remove_cv<const volatile int>::type).name() << endl;
}
輸出:
remove_cv<const int>: int
remove_cv<volatile int>: int
remove_cv<const volatile int>: int
這個也很簡單,但是非常有用。最后再來一個,相當有用的,它讓我們可以在編譯期間把一個數字作為二進制數來解釋:
#include <iostream>
using namespace std;
template <unsigned long N>
struct binary
{
??? static unsigned const value =
??????? binary<N/10>::value << 1 | N%10;
};
template <>
struct binary<0>
{
??? static unsigned const value = 0;
};
int main()
{
??? cout << "binary<1>: " << binary<1>::value << endl;
??? cout << "binary<11>: " << binary<11>::value << endl;
??? cout << "binary<101>: " << binary<101>::value << endl;
??? cout << "binary<111>: " << binary<111>::value << endl;
??? cout << "binary<1011101>: " << binary<1011101>::value << endl;
}
輸出:
binary<1>: 1
binary<11>: 3
binary<101>: 5
binary<111>: 7
binary<1011101>: 93
不過上面這個程序不容錯,換句話說,如果你寫 binary<123>::value ,編譯器不會阻止你,還會給出一個愚蠢的答案。如果要做一個容錯的解釋器,只需要玩一個小小的把戲(本人原創):
#include <iostream>
using namespace std;
namespace aux{
??? // 對于 0 和 1 以外的數,都不定義 value ,這樣在出現 0 1 之外的數
??? // 的時候,編譯器會抱怨找不到 value
??? template <unsigned long N>
??? struct binary
??? {};
???
??? template <>
??? struct binary<1>
??? { static unsigned const value = 1; };
???
??? template <>
??? struct binary<0>
??? { static unsigned const value = 0; };
}
template <unsigned long N>
struct binary
{
??? static unsigned const value =
??????? binary<N/10>::value << 1 | aux::binary<N%10>::value;
};
template <>
struct binary<0>
{
??? static unsigned const value = 0;
};
int main()
{
??? cout << "binary<1>: " << binary<1>::value << endl;
??? cout << "binary<11>: " << binary<11>::value << endl;
??? cout << "binary<101>: " << binary<101>::value << endl;
??? cout << "binary<111>: " << binary<111>::value << endl;
??? cout << "binary<1011101>: " << binary<1011101>::value << endl;
??? // 你可以 uncomment 下面這一行看看會發生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是如果你寫了 binary<123>::value 這樣的東西,編譯器就會抱怨了:
error C2039: 'value' : is not a member of 'aux::binary<N>'
??????? with
??????? [
??????????? N=2
??????? ]
// bla bla bla
好了,例子夠多了,我們可以稍微總結一下。CppTM 的好處在于:
把很多計算放到編譯期間完成,使得運行效率大為提高 由于計算在編譯期間完成,很多錯誤也可以在編譯期間發現,程序員不用到了程序開始跑了才進入痛苦的調試 有一些事情,比如 remove_cv ,在運行期間還的確不那么好做
4. 我怎么開始 Metaprogamming 呢?
在我們開始學習編程的時候,首先學到的是賦值、條件判斷、循環等等。在 CppTM 中,這些有了變化,如下:
循環 --> 模板遞歸
條件判斷 --> 模板偏特化
賦值 --> 沒有,變量的值一旦確定就不會變化(這對于 functional programming 是常事)
函數輸入輸出 --> 類型和常量
其實這些特征并不是什么旁門左道,正好相反,它具有 functional programming 的特征,符合圖靈機模型(感興趣的話可以看這篇 paper: C++ Templates are Turing Complete)。
5. 模板偏特化是個好東西,但是我每次都要把 if...then...else 映射成它,豈不是要累死?況且這編碼量也太大...
這是個好問題,好在在計算機科學里面有一句箴言:You can solve everything by adding an extra layer of abstraction. 使用模板偏特化進行條件判斷如此常用,我們完全應該把它抽象出來以備重用:
template <bool Cond, class Then, class Else>
struct if_
{ typedef Then type; };
template <class Then, class Else>
struct if_ <false, Then, Else>
{ typedef Else type; };
簡單吧? 雖然簡單,但是我們從此卻可以在更高的抽象層面上看問題,我們擺脫了用模板偏特化思考,現在可以直接用 if...then...else 來思考了。那么,上面解釋二進制數的程序就變成了下面這樣:
#include <iostream>
using namespace std;
template <bool Cond, class Then, class Else>
struct if_
{ typedef Then type; };
template <class Then, class Else>
struct if_ <false, Then, Else>
{ typedef Else type; };
namespace aux{
??? struct one
??? { static unsigned const value = 1; };
??? struct zero
??? { static unsigned const value = 0; };
??? struct other
??? {};
}
template <unsigned long N>
struct binary
{
??? static unsigned const value =
??????? if_ <N/10 == 0, aux::zero, binary<N/10> >::type::value << 1 |
??????? if_ <N%10 == 0, aux::zero,
??????????? if_ <N%10 == 1, aux::one, aux::other>::type
??????? >::type::value;
};
int main()
{
??? cout << "binary<1>: " << binary<1>::value << endl;
??? cout << "binary<11>: " << binary<11>::value << endl;
??? cout << "binary<101>: " << binary<101>::value << endl;
??? cout << "binary<111>: " << binary<111>::value << endl;
??? cout << "binary<1011101>: " << binary<1011101>::value << endl;
??? // 你可以 uncomment 下面這一行看看會發生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是現在不僅程序長度減少,而且相關的邏輯也用我們熟悉的 if...then...else 的方式來表達。多一層抽象果然威力強大!當然,循環還是要用模板遞歸的。
6. 流程控制的問題解決了,下面呢?我們是不是需要一些容器,像 STL 那樣?
如果要表達一個裝“類型”的容器,你會怎么做?象下面這樣么?
struct types
{
??? typedef int t1;
??? typdef long t2;
??? typedef std::vector<double> t3;
}
大概只要稍微明智一點,你就會馬上放棄這個想法,它太沒有通用性了。真正的啟示來自于 Lisp ,在 Lisp 中,表是最重要的數據結構,幾乎是“萬物皆表”。一個表由一個頭和一個尾組成,可以嵌套,空表用 nil 表示。這種簡單的概念卻有著不可思議的表達能力。如果我們用 C++ 來模擬,就是這樣:
template <class First, class Rest>
struct cons
{
??? typedef First first;
??? typedef Rest rest;
};
struct nil {};
現在我們需要表示一個類型列表就有章可循了:
typedef
??? cons<int,
??? cons<long,
??? cons<std::vector<double>,
??? nil> > > a_type_list;
它是遞歸的,從而我們可以很容易的用遞歸的方式來對它們作協操作,用來操作它們的,就是 Metafunction,前面的 if_ 就是一個 Metafunction。例如我們想選擇兩個類型中比較大的一個,可以寫一個 choose_larger Metafunction:
template <typename T1, typename T2>
struct choose_larger
{
??? typedef typename if_ <(sizeof(T1) > sizeof(T2)), T1, T2>::type type;
};
我們再次看到,由于有了 if_ ,我們的生活變得輕松多了。讓我們繼續向前發展,我們不想僅僅停留在兩個類型比大小上,我們希望選擇一個 type list 里面最大的那一個:
template <typename T> struct largest;
template <typename First, typename Rest>
struct largest<cons<First, Rest> >
??? : choose_larger<First, typename largest<Rest>::type>
{};
template< typename First >
struct largest<cons<First,nil> >
{ typedef First type; };
其實上面的也可以用上 if_ ,只不過我們現在還沒有寫出判斷一個類型是否為 nil 的 Metafunction ,這是件很簡單的事情,大家可以自己去寫寫看。有了它們,得到一個 type list 里面最大的元素就輕而易舉了:
#include <vector>
#include <iostream>
using namespace std;
//... 上面的那些 Metafunction
int main()
{
??? typedef
??????? cons<int,
??????? cons<long,
??????? cons<std::vector<double>,
??????? nil> > > type_list;
???
??? cout << typeid(largest<type_list>::type).name() << endl;
}
輸出:
class std::vector<double,class std::allocator<double> >
7. 的確很棒,但是這跟我們當年學數據結構以后,沒事就寫個鏈表玩玩差不多,在生產環境中可不能這樣,用什么通用的方法么?
終于到了這一步,在“重復發明輪子”足夠多次以后,終于就有人會出來發明通用輪子的,MPL 就是一個很好的嘗試,當然 Loki 也算是。
還記得 if_ 讓我們嘗到的甜頭么?聰明人在看到了這些甜頭以后,是決不會止步不前的,Dave Abrahams 和 Aleskey Gurtovoy 就是這樣的聰明人,他們發明了 MPL 。留到下一篇好了。
- Metaprogram: program that manipulates another program.
- Metaprogramming is not a new concept:
- Compiler is a metaprogram: manipulates your code and produces code in a lower level code
- Preprocessor
- YACC
2. Metaprogramming 的幾種途徑
- One approach: external to the language that is being manipulated
- Another approach: Domain language and host language are the same
第二種么,Preprocessor 當然算是了,template 則是重頭戲了。
3. 舉幾個簡單的例子先?
CppTM 領域最經典最簡單的例子莫過于計算階乘了,它簡單而有用,同時體現了 CppTM 的遞歸本質。我想絕大多數人當年學習遞歸的時候也是從這個例子開始的:
#include <iostream>
using namespace std;
template <int N>
struct factorial
{
??? static const int value = N * factorial<N - 1>::value;
};
template<>
struct factorial<0>
{
??? static const int value = 1;
};
int main()
{
??? cout << "factorial<10>: " << factorial<10>::value << endl;
??? cout << "sizeof char[factorial<4>::value]: " <<
??????????? sizeof(char[factorial<4>::value]) / sizeof(char) << endl;
}
輸出:
factorial<10>: 3628800
sizeof char[factorial<4>::value]: 24
這只是一個回顧,上面的簡單程序就不用我解釋了吧?下面的這個 remove_cv 算法會去掉參數類型的 const 和 volatile 修飾符(如果有的話),Boost type_traits 就是這么干的。
#include <iostream>
using namespace std;
template <class T> struct remove_cv
{ typedef T type; };
template <class T> struct remove_cv<const volatile T>
{ typedef T type; };
template <class T> struct remove_cv<const T>
{ typedef T type; };
template <class T> struct remove_cv<volatile T>
{ typedef T type; };
int main()
{
??? cout << "remove_cv<const int>: "
???????? << typeid(remove_cv<const int>::type).name() << endl;
??? cout << "remove_cv<volatile int>: "
???????? << typeid(remove_cv<volatile int>::type).name() << endl;
??? cout << "remove_cv<const volatile int>: "
???????? << typeid(remove_cv<const volatile int>::type).name() << endl;
}
輸出:
remove_cv<const int>: int
remove_cv<volatile int>: int
remove_cv<const volatile int>: int
這個也很簡單,但是非常有用。最后再來一個,相當有用的,它讓我們可以在編譯期間把一個數字作為二進制數來解釋:
#include <iostream>
using namespace std;
template <unsigned long N>
struct binary
{
??? static unsigned const value =
??????? binary<N/10>::value << 1 | N%10;
};
template <>
struct binary<0>
{
??? static unsigned const value = 0;
};
int main()
{
??? cout << "binary<1>: " << binary<1>::value << endl;
??? cout << "binary<11>: " << binary<11>::value << endl;
??? cout << "binary<101>: " << binary<101>::value << endl;
??? cout << "binary<111>: " << binary<111>::value << endl;
??? cout << "binary<1011101>: " << binary<1011101>::value << endl;
}
輸出:
binary<1>: 1
binary<11>: 3
binary<101>: 5
binary<111>: 7
binary<1011101>: 93
不過上面這個程序不容錯,換句話說,如果你寫 binary<123>::value ,編譯器不會阻止你,還會給出一個愚蠢的答案。如果要做一個容錯的解釋器,只需要玩一個小小的把戲(本人原創):
#include <iostream>
using namespace std;
namespace aux{
??? // 對于 0 和 1 以外的數,都不定義 value ,這樣在出現 0 1 之外的數
??? // 的時候,編譯器會抱怨找不到 value
??? template <unsigned long N>
??? struct binary
??? {};
???
??? template <>
??? struct binary<1>
??? { static unsigned const value = 1; };
???
??? template <>
??? struct binary<0>
??? { static unsigned const value = 0; };
}
template <unsigned long N>
struct binary
{
??? static unsigned const value =
??????? binary<N/10>::value << 1 | aux::binary<N%10>::value;
};
template <>
struct binary<0>
{
??? static unsigned const value = 0;
};
int main()
{
??? cout << "binary<1>: " << binary<1>::value << endl;
??? cout << "binary<11>: " << binary<11>::value << endl;
??? cout << "binary<101>: " << binary<101>::value << endl;
??? cout << "binary<111>: " << binary<111>::value << endl;
??? cout << "binary<1011101>: " << binary<1011101>::value << endl;
??? // 你可以 uncomment 下面這一行看看會發生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是如果你寫了 binary<123>::value 這樣的東西,編譯器就會抱怨了:
error C2039: 'value' : is not a member of 'aux::binary<N>'
??????? with
??????? [
??????????? N=2
??????? ]
// bla bla bla
好了,例子夠多了,我們可以稍微總結一下。CppTM 的好處在于:
4. 我怎么開始 Metaprogamming 呢?
在我們開始學習編程的時候,首先學到的是賦值、條件判斷、循環等等。在 CppTM 中,這些有了變化,如下:
循環 --> 模板遞歸
條件判斷 --> 模板偏特化
賦值 --> 沒有,變量的值一旦確定就不會變化(這對于 functional programming 是常事)
函數輸入輸出 --> 類型和常量
其實這些特征并不是什么旁門左道,正好相反,它具有 functional programming 的特征,符合圖靈機模型(感興趣的話可以看這篇 paper: C++ Templates are Turing Complete)。
5. 模板偏特化是個好東西,但是我每次都要把 if...then...else 映射成它,豈不是要累死?況且這編碼量也太大...
這是個好問題,好在在計算機科學里面有一句箴言:You can solve everything by adding an extra layer of abstraction. 使用模板偏特化進行條件判斷如此常用,我們完全應該把它抽象出來以備重用:
template <bool Cond, class Then, class Else>
struct if_
{ typedef Then type; };
template <class Then, class Else>
struct if_ <false, Then, Else>
{ typedef Else type; };
簡單吧? 雖然簡單,但是我們從此卻可以在更高的抽象層面上看問題,我們擺脫了用模板偏特化思考,現在可以直接用 if...then...else 來思考了。那么,上面解釋二進制數的程序就變成了下面這樣:
#include <iostream>
using namespace std;
template <bool Cond, class Then, class Else>
struct if_
{ typedef Then type; };
template <class Then, class Else>
struct if_ <false, Then, Else>
{ typedef Else type; };
namespace aux{
??? struct one
??? { static unsigned const value = 1; };
??? struct zero
??? { static unsigned const value = 0; };
??? struct other
??? {};
}
template <unsigned long N>
struct binary
{
??? static unsigned const value =
??????? if_ <N/10 == 0, aux::zero, binary<N/10> >::type::value << 1 |
??????? if_ <N%10 == 0, aux::zero,
??????????? if_ <N%10 == 1, aux::one, aux::other>::type
??????? >::type::value;
};
int main()
{
??? cout << "binary<1>: " << binary<1>::value << endl;
??? cout << "binary<11>: " << binary<11>::value << endl;
??? cout << "binary<101>: " << binary<101>::value << endl;
??? cout << "binary<111>: " << binary<111>::value << endl;
??? cout << "binary<1011101>: " << binary<1011101>::value << endl;
??? // 你可以 uncomment 下面這一行看看會發生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是現在不僅程序長度減少,而且相關的邏輯也用我們熟悉的 if...then...else 的方式來表達。多一層抽象果然威力強大!當然,循環還是要用模板遞歸的。
6. 流程控制的問題解決了,下面呢?我們是不是需要一些容器,像 STL 那樣?
如果要表達一個裝“類型”的容器,你會怎么做?象下面這樣么?
struct types
{
??? typedef int t1;
??? typdef long t2;
??? typedef std::vector<double> t3;
}
大概只要稍微明智一點,你就會馬上放棄這個想法,它太沒有通用性了。真正的啟示來自于 Lisp ,在 Lisp 中,表是最重要的數據結構,幾乎是“萬物皆表”。一個表由一個頭和一個尾組成,可以嵌套,空表用 nil 表示。這種簡單的概念卻有著不可思議的表達能力。如果我們用 C++ 來模擬,就是這樣:
template <class First, class Rest>
struct cons
{
??? typedef First first;
??? typedef Rest rest;
};
struct nil {};
現在我們需要表示一個類型列表就有章可循了:
typedef
??? cons<int,
??? cons<long,
??? cons<std::vector<double>,
??? nil> > > a_type_list;
它是遞歸的,從而我們可以很容易的用遞歸的方式來對它們作協操作,用來操作它們的,就是 Metafunction,前面的 if_ 就是一個 Metafunction。例如我們想選擇兩個類型中比較大的一個,可以寫一個 choose_larger Metafunction:
template <typename T1, typename T2>
struct choose_larger
{
??? typedef typename if_ <(sizeof(T1) > sizeof(T2)), T1, T2>::type type;
};
我們再次看到,由于有了 if_ ,我們的生活變得輕松多了。讓我們繼續向前發展,我們不想僅僅停留在兩個類型比大小上,我們希望選擇一個 type list 里面最大的那一個:
template <typename T> struct largest;
template <typename First, typename Rest>
struct largest<cons<First, Rest> >
??? : choose_larger<First, typename largest<Rest>::type>
{};
template< typename First >
struct largest<cons<First,nil> >
{ typedef First type; };
其實上面的也可以用上 if_ ,只不過我們現在還沒有寫出判斷一個類型是否為 nil 的 Metafunction ,這是件很簡單的事情,大家可以自己去寫寫看。有了它們,得到一個 type list 里面最大的元素就輕而易舉了:
#include <vector>
#include <iostream>
using namespace std;
//... 上面的那些 Metafunction
int main()
{
??? typedef
??????? cons<int,
??????? cons<long,
??????? cons<std::vector<double>,
??????? nil> > > type_list;
???
??? cout << typeid(largest<type_list>::type).name() << endl;
}
輸出:
class std::vector<double,class std::allocator<double> >
7. 的確很棒,但是這跟我們當年學數據結構以后,沒事就寫個鏈表玩玩差不多,在生產環境中可不能這樣,用什么通用的方法么?
終于到了這一步,在“重復發明輪子”足夠多次以后,終于就有人會出來發明通用輪子的,MPL 就是一個很好的嘗試,當然 Loki 也算是。
還記得 if_ 讓我們嘗到的甜頭么?聰明人在看到了這些甜頭以后,是決不會止步不前的,Dave Abrahams 和 Aleskey Gurtovoy 就是這樣的聰明人,他們發明了 MPL 。留到下一篇好了。
總結
以上是生活随笔為你收集整理的Template Metaprogramming的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vc6怎么看错误在哪_周杰伦超话第一!微
- 下一篇: elasticsearch 数据类型_基