Template Metaprogramming
生活随笔
收集整理的這篇文章主要介紹了
Template Metaprogramming
小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
Template Metaprogramming
1. 何謂 Metaprogramming?
2. Metaprogramming 的幾種途徑
第二種么,Preprocessor 當(dāng)然算是了,template 則是重頭戲了。
3. 舉幾個(gè)簡(jiǎn)單的例子先?
CppTM 領(lǐng)域最經(jīng)典最簡(jiǎn)單的例子莫過(guò)于計(jì)算階乘了,它簡(jiǎn)單而有用,同時(shí)體現(xiàn)了 CppTM 的遞歸本質(zhì)。我想絕大多數(shù)人當(dāng)年學(xué)習(xí)遞歸的時(shí)候也是從這個(gè)例子開始的:
#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
這只是一個(gè)回顧,上面的簡(jiǎn)單程序就不用我解釋了吧?下面的這個(gè) remove_cv 算法會(huì)去掉參數(shù)類型的 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
這個(gè)也很簡(jiǎn)單,但是非常有用。最后再來(lái)一個(gè),相當(dāng)有用的,它讓我們可以在編譯期間把一個(gè)數(shù)字作為二進(jìn)制數(shù)來(lái)解釋:
#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
不過(guò)上面這個(gè)程序不容錯(cuò),換句話說(shuō),如果你寫 binary<123>::value ,編譯器不會(huì)阻止你,還會(huì)給出一個(gè)愚蠢的答案。如果要做一個(gè)容錯(cuò)的解釋器,只需要玩一個(gè)小小的把戲(本人原創(chuàng)):
#include <iostream>
using namespace std;
namespace aux{
??? // 對(duì)于 0 和 1 以外的數(shù),都不定義 value ,這樣在出現(xiàn) 0 1 之外的數(shù)
??? // 的時(shí)候,編譯器會(huì)抱怨找不到 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 下面這一行看看會(huì)發(fā)生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是如果你寫了 binary<123>::value 這樣的東西,編譯器就會(huì)抱怨了:
error C2039: 'value' : is not a member of 'aux::binary<N>'
??????? with
??????? [
??????????? N=2
??????? ]
// bla bla bla
好了,例子夠多了,我們可以稍微總結(jié)一下。CppTM 的好處在于:
把很多計(jì)算放到編譯期間完成,使得運(yùn)行效率大為提高 由于計(jì)算在編譯期間完成,很多錯(cuò)誤也可以在編譯期間發(fā)現(xiàn),程序員不用到了程序開始跑了才進(jìn)入痛苦的調(diào)試 有一些事情,比如 remove_cv ,在運(yùn)行期間還的確不那么好做
4. 我怎么開始 Metaprogamming 呢?
在我們開始學(xué)習(xí)編程的時(shí)候,首先學(xué)到的是賦值、條件判斷、循環(huán)等等。在 CppTM 中,這些有了變化,如下:
循環(huán) --> 模板遞歸
條件判斷 --> 模板偏特化
賦值 --> 沒有,變量的值一旦確定就不會(huì)變化(這對(duì)于 functional programming 是常事)
函數(shù)輸入輸出 --> 類型和常量
其實(shí)這些特征并不是什么旁門左道,正好相反,它具有 functional programming 的特征,符合圖靈機(jī)模型(感興趣的話可以看這篇 paper: C++ Templates are Turing Complete)。
5. 模板偏特化是個(gè)好東西,但是我每次都要把 if...then...else 映射成它,豈不是要累死?況且這編碼量也太大...
這是個(gè)好問(wèn)題,好在在計(jì)算機(jī)科學(xué)里面有一句箴言:You can solve everything by adding an extra layer of abstraction. 使用模板偏特化進(jìn)行條件判斷如此常用,我們完全應(yīng)該把它抽象出來(lái)以備重用:
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; };
簡(jiǎn)單吧? 雖然簡(jiǎn)單,但是我們從此卻可以在更高的抽象層面上看問(wèn)題,我們擺脫了用模板偏特化思考,現(xiàn)在可以直接用 if...then...else 來(lái)思考了。那么,上面解釋二進(jìn)制數(shù)的程序就變成了下面這樣:
#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 下面這一行看看會(huì)發(fā)生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是現(xiàn)在不僅程序長(zhǎng)度減少,而且相關(guān)的邏輯也用我們熟悉的 if...then...else 的方式來(lái)表達(dá)。多一層抽象果然威力強(qiáng)大!當(dāng)然,循環(huán)還是要用模板遞歸的。
6. 流程控制的問(wèn)題解決了,下面呢?我們是不是需要一些容器,像 STL 那樣?
如果要表達(dá)一個(gè)裝“類型”的容器,你會(huì)怎么做?象下面這樣么?
struct types
{
??? typedef int t1;
??? typdef long t2;
??? typedef std::vector<double> t3;
}
大概只要稍微明智一點(diǎn),你就會(huì)馬上放棄這個(gè)想法,它太沒有通用性了。真正的啟示來(lái)自于 Lisp ,在 Lisp 中,表是最重要的數(shù)據(jù)結(jié)構(gòu),幾乎是“萬(wàn)物皆表”。一個(gè)表由一個(gè)頭和一個(gè)尾組成,可以嵌套,空表用 nil 表示。這種簡(jiǎn)單的概念卻有著不可思議的表達(dá)能力。如果我們用 C++ 來(lái)模擬,就是這樣:
template <class First, class Rest>
struct cons
{
??? typedef First first;
??? typedef Rest rest;
};
struct nil {};
現(xiàn)在我們需要表示一個(gè)類型列表就有章可循了:
typedef
??? cons<int,
??? cons<long,
??? cons<std::vector<double>,
??? nil> > > a_type_list;
它是遞歸的,從而我們可以很容易的用遞歸的方式來(lái)對(duì)它們作協(xié)操作,用來(lái)操作它們的,就是 Metafunction,前面的 if_ 就是一個(gè) Metafunction。例如我們想選擇兩個(gè)類型中比較大的一個(gè),可以寫一個(gè) choose_larger Metafunction:
template <typename T1, typename T2>
struct choose_larger
{
??? typedef typename if_ <(sizeof(T1) > sizeof(T2)), T1, T2>::type type;
};
我們?cè)俅慰吹?#xff0c;由于有了 if_ ,我們的生活變得輕松多了。讓我們繼續(xù)向前發(fā)展,我們不想僅僅停留在兩個(gè)類型比大小上,我們希望選擇一個(gè) type list 里面最大的那一個(gè):
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; };
其實(shí)上面的也可以用上 if_ ,只不過(guò)我們現(xiàn)在還沒有寫出判斷一個(gè)類型是否為 nil 的 Metafunction ,這是件很簡(jiǎn)單的事情,大家可以自己去寫寫看。有了它們,得到一個(gè) 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. 的確很棒,但是這跟我們當(dāng)年學(xué)數(shù)據(jù)結(jié)構(gòu)以后,沒事就寫個(gè)鏈表玩玩差不多,在生產(chǎn)環(huán)境中可不能這樣,用什么通用的方法么?
終于到了這一步,在“重復(fù)發(fā)明輪子”足夠多次以后,終于就有人會(huì)出來(lái)發(fā)明通用輪子的,MPL 就是一個(gè)很好的嘗試,當(dāng)然 Loki 也算是。
還記得 if_ 讓我們嘗到的甜頭么?聰明人在看到了這些甜頭以后,是決不會(huì)止步不前的,Dave Abrahams 和 Aleskey Gurtovoy 就是這樣的聰明人,他們發(fā)明了 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 當(dāng)然算是了,template 則是重頭戲了。
3. 舉幾個(gè)簡(jiǎn)單的例子先?
CppTM 領(lǐng)域最經(jīng)典最簡(jiǎn)單的例子莫過(guò)于計(jì)算階乘了,它簡(jiǎn)單而有用,同時(shí)體現(xiàn)了 CppTM 的遞歸本質(zhì)。我想絕大多數(shù)人當(dāng)年學(xué)習(xí)遞歸的時(shí)候也是從這個(gè)例子開始的:
#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
這只是一個(gè)回顧,上面的簡(jiǎn)單程序就不用我解釋了吧?下面的這個(gè) remove_cv 算法會(huì)去掉參數(shù)類型的 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
這個(gè)也很簡(jiǎn)單,但是非常有用。最后再來(lái)一個(gè),相當(dāng)有用的,它讓我們可以在編譯期間把一個(gè)數(shù)字作為二進(jìn)制數(shù)來(lái)解釋:
#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
不過(guò)上面這個(gè)程序不容錯(cuò),換句話說(shuō),如果你寫 binary<123>::value ,編譯器不會(huì)阻止你,還會(huì)給出一個(gè)愚蠢的答案。如果要做一個(gè)容錯(cuò)的解釋器,只需要玩一個(gè)小小的把戲(本人原創(chuàng)):
#include <iostream>
using namespace std;
namespace aux{
??? // 對(duì)于 0 和 1 以外的數(shù),都不定義 value ,這樣在出現(xiàn) 0 1 之外的數(shù)
??? // 的時(shí)候,編譯器會(huì)抱怨找不到 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 下面這一行看看會(huì)發(fā)生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是如果你寫了 binary<123>::value 這樣的東西,編譯器就會(huì)抱怨了:
error C2039: 'value' : is not a member of 'aux::binary<N>'
??????? with
??????? [
??????????? N=2
??????? ]
// bla bla bla
好了,例子夠多了,我們可以稍微總結(jié)一下。CppTM 的好處在于:
4. 我怎么開始 Metaprogamming 呢?
在我們開始學(xué)習(xí)編程的時(shí)候,首先學(xué)到的是賦值、條件判斷、循環(huán)等等。在 CppTM 中,這些有了變化,如下:
循環(huán) --> 模板遞歸
條件判斷 --> 模板偏特化
賦值 --> 沒有,變量的值一旦確定就不會(huì)變化(這對(duì)于 functional programming 是常事)
函數(shù)輸入輸出 --> 類型和常量
其實(shí)這些特征并不是什么旁門左道,正好相反,它具有 functional programming 的特征,符合圖靈機(jī)模型(感興趣的話可以看這篇 paper: C++ Templates are Turing Complete)。
5. 模板偏特化是個(gè)好東西,但是我每次都要把 if...then...else 映射成它,豈不是要累死?況且這編碼量也太大...
這是個(gè)好問(wèn)題,好在在計(jì)算機(jī)科學(xué)里面有一句箴言:You can solve everything by adding an extra layer of abstraction. 使用模板偏特化進(jìn)行條件判斷如此常用,我們完全應(yīng)該把它抽象出來(lái)以備重用:
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; };
簡(jiǎn)單吧? 雖然簡(jiǎn)單,但是我們從此卻可以在更高的抽象層面上看問(wèn)題,我們擺脫了用模板偏特化思考,現(xiàn)在可以直接用 if...then...else 來(lái)思考了。那么,上面解釋二進(jìn)制數(shù)的程序就變成了下面這樣:
#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 下面這一行看看會(huì)發(fā)生什么
??? //cout << "binary<123>: " << binary<123>::value << endl;
}
輸出還是一樣,但是現(xiàn)在不僅程序長(zhǎng)度減少,而且相關(guān)的邏輯也用我們熟悉的 if...then...else 的方式來(lái)表達(dá)。多一層抽象果然威力強(qiáng)大!當(dāng)然,循環(huán)還是要用模板遞歸的。
6. 流程控制的問(wèn)題解決了,下面呢?我們是不是需要一些容器,像 STL 那樣?
如果要表達(dá)一個(gè)裝“類型”的容器,你會(huì)怎么做?象下面這樣么?
struct types
{
??? typedef int t1;
??? typdef long t2;
??? typedef std::vector<double> t3;
}
大概只要稍微明智一點(diǎn),你就會(huì)馬上放棄這個(gè)想法,它太沒有通用性了。真正的啟示來(lái)自于 Lisp ,在 Lisp 中,表是最重要的數(shù)據(jù)結(jié)構(gòu),幾乎是“萬(wàn)物皆表”。一個(gè)表由一個(gè)頭和一個(gè)尾組成,可以嵌套,空表用 nil 表示。這種簡(jiǎn)單的概念卻有著不可思議的表達(dá)能力。如果我們用 C++ 來(lái)模擬,就是這樣:
template <class First, class Rest>
struct cons
{
??? typedef First first;
??? typedef Rest rest;
};
struct nil {};
現(xiàn)在我們需要表示一個(gè)類型列表就有章可循了:
typedef
??? cons<int,
??? cons<long,
??? cons<std::vector<double>,
??? nil> > > a_type_list;
它是遞歸的,從而我們可以很容易的用遞歸的方式來(lái)對(duì)它們作協(xié)操作,用來(lái)操作它們的,就是 Metafunction,前面的 if_ 就是一個(gè) Metafunction。例如我們想選擇兩個(gè)類型中比較大的一個(gè),可以寫一個(gè) choose_larger Metafunction:
template <typename T1, typename T2>
struct choose_larger
{
??? typedef typename if_ <(sizeof(T1) > sizeof(T2)), T1, T2>::type type;
};
我們?cè)俅慰吹?#xff0c;由于有了 if_ ,我們的生活變得輕松多了。讓我們繼續(xù)向前發(fā)展,我們不想僅僅停留在兩個(gè)類型比大小上,我們希望選擇一個(gè) type list 里面最大的那一個(gè):
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; };
其實(shí)上面的也可以用上 if_ ,只不過(guò)我們現(xiàn)在還沒有寫出判斷一個(gè)類型是否為 nil 的 Metafunction ,這是件很簡(jiǎn)單的事情,大家可以自己去寫寫看。有了它們,得到一個(gè) 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. 的確很棒,但是這跟我們當(dāng)年學(xué)數(shù)據(jù)結(jié)構(gòu)以后,沒事就寫個(gè)鏈表玩玩差不多,在生產(chǎn)環(huán)境中可不能這樣,用什么通用的方法么?
終于到了這一步,在“重復(fù)發(fā)明輪子”足夠多次以后,終于就有人會(huì)出來(lái)發(fā)明通用輪子的,MPL 就是一個(gè)很好的嘗試,當(dāng)然 Loki 也算是。
還記得 if_ 讓我們嘗到的甜頭么?聰明人在看到了這些甜頭以后,是決不會(huì)止步不前的,Dave Abrahams 和 Aleskey Gurtovoy 就是這樣的聰明人,他們發(fā)明了 MPL 。留到下一篇好了。
總結(jié)
以上是生活随笔為你收集整理的Template Metaprogramming的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: vc6怎么看错误在哪_周杰伦超话第一!微
- 下一篇: 水滴石穿C语言之可变参数问题