程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++11模版元編程

C++11模版元編程

編輯:關於C++
1.概述     模版元編程(template metaprogram)是C++中最復雜也是威力最強大的編程范式,它是一種可以創建和操縱程序的程序。模版元編程完全不同於普通的運行期程序,它很獨特,因為模版元程序的執行完全是在編譯期,並且模版元程序操縱的數據不能是運行時變量,只能是編譯期常量,不可修改,另外它用到的語法元素也是相當有限,不能使用運行期的一些語法,比如if-else,for等語句都不能用。因此,模版元編程需要很多技巧,常常需要類型重定義、枚舉常量、繼承、模板偏特化等方法來配合,因此編寫模版元編程比較復雜也比較困難。     現在C++11新增了一些模版元相關的特性,不僅可以讓我們編寫模版元程序變得更容易,還進一步增強了泛型編程的能力,比如type_traits讓我們不必再重復發明輪子了,給我們提供了大量便利的元函數,還提供了可變模板參數和tuple,讓模版元編程“如虎添翼”。本文將向讀者展示C++11中模版元編程常用的技巧和具體應用。   2.模版元基本概念     模版元程序由元數據和元函數組成,元數據就是元編程可以操作的數據,即C++編譯器在編譯期可以操作的數據。元數據不是運行期變量,只能是編譯期常量,不能修改,常見的元數據有enum枚舉常量、靜態常量、基本類型和自定義類型等。     元函數是模板元編程中用於操作處理元數據的“構件”,可以在編譯期被“調用”,因為它的功能和形式和運行時的函數類似,而被稱為元函數,它是元編程中最重要的構件。元函數實際上表現為C++的一個類、模板類或模板函數,它的通常形式如下:   template<int N, int M> struct meta_func {     static const value = N+M; }   調用元函數獲取value值:cout<<meta_func<1, 2>::value<<endl;     meta_func的執行過程是在編譯期完成的,實際執行程序時,是沒有計算動作而是直接使用編譯期的計算結果的。元函數只處理元數據,元數據是編譯期常量和類型,所以下面的代碼是編譯不過的:   int i = 1, j = 2; meta_func<i, j>::value; //錯誤,元函數無法處理運行時普通數據   模板元編程產生的源程序是在編譯期執行的程序,因此它首先要遵循C++和模板的語法,但是它操作的對象不是運行時普通的變量,因此不能使用運行時的C++關鍵字(如if、else、for),可用的語法元素相當有限,最常用的是:   enum、static const,用來定義編譯期的整數常量; typedef/using,用於定義元數據; T、Args...,聲明元數據類型; template,主要用於定義元函數; "::",域運算符,用於解析類型作用域獲取計算結果(元數據)。 如果模板元編程中需要if-else、for等邏輯時該怎麼辦呢?   模板元中的if-else可以通過type_traits來實現,它不僅僅可以在編譯期做判斷,還可以做計算、查詢、轉換和選擇。   模板元中的for等邏輯可以通過遞歸、重載、和模板特化(偏特化)等方法實現。   下面來看看C++11提供的模版元基礎庫type_traits。   3.type_traits     type_traits是C++11提供的模板元基礎庫,通過type_traits可以實現在編譯期計算、查詢、判斷、轉換和選擇,提供了模板元編程需要的一些常用元函數。下面來看看一些基本的type_traits的基本用法。     最簡單的一個type_traits是定義編譯期常量的元函數integral_constant,它的定義如下:   template< class T, T v > struct integral_constant;   借助這個簡單的trait,我們可以很方便地定義編譯期常量,比如定義一個值為1的int常量可以這樣定義:   using one_type = std::integral_constant<int, 1>; 或者   template<class T> struct one_type : std::integral_constant<int, 1>{};   獲取常量則通過one_type::value來獲取,這種定義編譯期常量的方式相比C++98/03要簡單,在C++98/03中定義編譯期常量一般是這樣定義的:     template<class T> struct one_type {     enum{value = 1}; };   template<class T> struct one_type {     static const int value = 1; };     可以看到,通過C++11的type_traits提供的一個簡單的integral_constant就可以很方便的定義編譯期常量,而無需再去通過定義enum和static const變量方式去定義編譯期常量了,這也為定義編譯期常量提供了另外一種方法。C++11的type_traits已經提供了編譯期的true和false,是通過integral_constant來定義的:   typedef  integral_constant<bool, true> true_type; typedef  integral_constant<bool, false> false_type;   除了這些基本的元函數之外,type_traits還提供了豐富的元函數,比如用於編譯期判斷的元函數:         這只是列舉一小部分的type_traits元函數,type_traits提供了上百個方便的元函數,讀者可以參考http://en.cppreference.com/w/cpp/header/type_traits,這些基本的元函數用法比較簡單:     #include <iostream> #include <type_traits>   int main() {   std::cout << "int: " << std::is_const<int>::value << std::endl;   std::cout << "const int: " << std::is_const<const int>::value << std::endl;     //判斷類型是否相同   std::cout<< std::is_same<int, int>::value<<"\n";// true   std::cout<< std::is_same<int, unsignedint>::value<<"\n";// false     //添加、移除const   cout << std::is_same<const int, add_const<int>::type>::value << endl;   cout << std::is_same<int, remove_const<const int>::type>::value << endl;     //添加引用   cout << std::is_same<int&, add_lvalue_reference<int>::type>::value << endl;   cout << std::is_same<int&&, add_rvalue_reference<int>::type>::value << endl;     //取公共類型   typedef std::common_type<unsigned char, short, int>::type NumericType;   cout << std::is_same<int, NumericType>::value << endl;     return 0; }     type_traits還提供了編譯期選擇traits:std::conditional,它在編譯期根據一個判斷式選擇兩個類型中的一個,和條件表達式的語義類似,類似於一個三元表達式。它的原型是:   template< bool B, class T, class F > struct conditional; 用法比較簡單:     #include <iostream> #include <type_traits>   int main()  {     typedef std::conditional<true,int,float>::type A;               // int     typedef std::conditional<false,int,float>::type B;              // float       typedef std::conditional<(sizeof(long long) >sizeof(long double)),     long long, long double>::type max_size_t;       cout<<typeid(max_size_t).name()<<endl;  //long double }     另外一個常用的type_traits是std::decay(朽化),它對於普通類型來說std::decay(朽化)是移除引用和cv符,大大簡化了我們的書寫。除了普通類型之外,std::decay還可以用於數組和函數,具體的轉換規則是這樣的:     先移除T類型的引用,得到類型U,U定義為remove_reference<T>::type。   如果is_array<U>::value為 true,修改類型type為remove_extent<U>::type *。 否則,如果is_function<U>::value為 true,修改類型type將為add_pointer<U>::type。 否則,修改類型type為 remove_cv<U>::type。 std::decay的基本用法:     typedef std::decay<int>::type A;           // int typedef std::decay<int&>::type B;          // int typedef std::decay<int&&>::type C;         // int typedef std::decay<constint&>::type D;    // int typedef std::decay<int[2]>::type E;        // int* typedef std::decay<int(int)>::type F;      // int(*)(int)     std::decay除了移除普通類型的cv符的作用之外,還可以將函數類型轉換為函數指針類型,從而將函數指針變量保存起來,以便在後面延遲執行,比如下面的例子。     template<typename F> struct SimpFunction {     using FnType = typename std::decay<F>::type;//先移除引用再添加指針       SimpFunction(F& f) : m_fn(f){}       void Run()     {         m_fn();     }       FnType m_fn; };     如果要保存輸入的函數,則先要獲取函數對應的函數指針類型,這時就可以用std::decay來獲取函數指針類型了,using FnType = typename std::decay<F>::type;實現函數指針類型的定義。type_traits還提供了獲取可調用對象返回類型的元函數:std::result_of,它的基本用法:     int fn(int) {return int();}                            // function typedef int(&fn_ref)(int);                             // function reference typedef int(*fn_ptr)(int);                             // function pointer struct fn_class { int operator()(int i){return i;} };  // function-like class   int main() {   typedef std::result_of<decltype(fn)&(int)>::type A;  // int   typedef std::result_of<fn_ref(int)>::type B;         // int   typedef std::result_of<fn_ptr(int)>::type C;         // int   typedef std::result_of<fn_class(int)>::type D;       // int }     type_traits還提供了一個很有用的元函數std::enable_if,它利用SFINAE(substitude failure is not an error)特性,根據條件選擇重載函數的元函數std::enable_if,它的原型是:   template<bool B, class T = void> struct enable_if;     根據enable_if的字面意思就可以知道,它使得函數在判斷條件B僅僅為true時才有效,它的基本用法:     template <class T> typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t) {     return t; } auto r = foo(1); //返回整數1 auto r1 = foo(1.2); //返回浮點數1.2 auto r2 = foo(“test”); //compile error     在上面的例子中對模板參數T做了限定,即只能是arithmetic(整型和浮點型)類型,如果為非arithmetic類型,則編譯不通過,因為std::enable_if只對滿足判斷式條件的函數有效,對其他函數無效。     可以通過enable_if來實現編譯期的if-else邏輯,比如下面的例子通過enable_if和條件判斷式來將入參分為兩大類,從而滿足所有的入參類型:     template <class T> typename std::enable_if<std::is_arithmetic<T>::value, int>::type foo1(T t) {     cout << t << endl;     return 0; }   template <class T> typename std::enable_if<!std::is_arithmetic<T>::value, int>::type foo1(T &t) {     cout << typeid(T).name() << endl;     return 1; }     對於arithmetic類型的入參則返回0,對於非arithmetic的類型則返回1,通過arithmetic將所有的入參類型分成了兩大類進行處理。從上面的例子還可以看到,std::enable_if可以實現強大的重載機制,因為通常必須是參數不同才能重載,如果只有返回值不同是不能重載的,而在上面的例子中,返回類型相同的函數都可以重載。     C++11的type_traits提供了近百個在編譯期計算、查詢、判斷、轉換和選擇的元函數,為我們編寫元程序提供了很大的便利。如果說C++11的type_traits讓模版元編程變得簡單,那麼C++11提供的可變模板參數和tuple則進一步增強了模板元編程。   4.可變模板參數     C++11的可變模版參數(variadic templates)是C++11新增的最強大的特性之一,它對參數進行了高度泛化,它能表示0到任意個數、任意類型的參數。關於它的用法和使用技巧讀者可以參考筆者在程序員2015年2月A上的文章:泛化之美--C++11可變模版參數的妙用,這裡不再贅述,這裡將要展示的如何借助可變模板參數實現一些編譯期算法,比如獲取最大值、判斷是否包含了某個類型、根據索引查找類型、獲取類型的索引和遍歷類型等算法。實現這些算法需要結合type_traits或其它C++11特性,下面來看看這些編譯期算法是如何實現的。     編譯期從一個整形序列中獲取最大值:     //獲取最大的整數 template <size_t arg, size_t... rest> struct IntegerMax;   template <size_t arg> struct IntegerMax<arg> : std::integral_constant<size_t, arg> { };   template <size_t arg1, size_t arg2, size_t... rest> struct IntegerMax<arg1, arg2, rest...> : std::integral_constant<size_t, arg1 >= arg2 ? IntegerMax<arg1, rest...>::value :     IntegerMax<arg2, rest...>::value > { };     這個IntegerMax的實現用到了type_traits中的std::integral_const,它在展開參數包的過程中,不斷的比較,直到所有的參數都比較完,最終std::integral_const的value值即為最大值。它的使用很簡單:   cout << IntegerMax<2, 5, 1, 7, 3>::value << endl; //value為7     我們可以在IntegerMax的基礎上輕松的實現獲取最大內存對齊值的元函數MaxAlign。     編譯期獲取最大的align:     template<typename... Args> struct MaxAlign : std::integral_constant<int, IntegerMax<std::alignment_of<Args>::value...>::value>{}; cout << MaxAlign<int, short, double, char>::value << endl; //value為8     編譯判斷是否包含了某種類型: template < typename T, typename... List > struct Contains;   template < typename T, typename Head, typename... Rest > struct Contains<T, Head, Rest...>     : std::conditional< std::is_same<T, Head>::value, std::true_type, Contains<T, Rest... >> ::type{};   template < typename T > struct Contains<T> : std::false_type{}; 用法:cout<<Contains<int, char, double, int, short>::value<<endl; //輸出true     這個Contains的實現用到了type_traits的std::conditional、std::is_same、std::true_type和std::false_type,它的實現思路是在展開參數包的過程中不斷的比較類型是否相同,如果相同則設置值為true,否則設置為false。           編譯期獲取類型的索引:     template < typename T, typename... List > struct IndexOf;   template < typename T, typename Head, typename... Rest > struct IndexOf<T, Head, Rest...> {     enum{ value = IndexOf<T, Rest...>::value+1 }; };   template < typename T, typename... Rest > struct IndexOf<T, T, Rest...> {     enum{ value = 0 }; };   template < typename T > struct IndexOf<T> {     enum{value = -1}; };     用法:cout<< IndexOf<int, double, short, char, int, float>::value<<endl; //輸出3     這個IndexOf的實現比較簡單,在展開參數包的過程中看是否匹配到特化的IndexOf<T, T, Rest...>,如果匹配上則終止遞歸將之前的value累加起來得到目標類型的索引位置,否則將value加1,如果所有的類型中都沒有對應的類型則返回-1;     編譯期根據索引位置查找類型:     template<int index, typename... Types> struct At;   template<int index, typename First, typename... Types> struct At<index, First, Types...> {     using type = typename At<index - 1, Types...>::type; };   template<typename T, typename... Types> struct At<0, T, Types...> {     using type = T; };     用法: using T = At<1, int, double, char>::type;     cout << typeid(T).name() << endl; //輸出double     At的實現比較簡單,只要在展開參數包的過程中,不斷的將索引遞減至0時為止即可獲取對應索引位置的類型。接下來看看如何在編譯期遍歷類型。     template<typename T> void printarg() {     cout << typeid(T).name() << endl; }   template<typename... Args> void for_each()  {     std::initializer_list<int>{(printarg<Args>(), 0)...}; } 用法:for_each<int,double>();//將輸出int double     這裡for_each的實現是通過初始化列表和逗號表達式來遍歷可變模板參數的。     可以看到,借助可變模板參數和type_traits以及模板偏特化和遞歸等方式我們可以實現一些有用的編譯期算法,這些算法為我們編寫應用層級別的代碼奠定了基礎,後面模板元編程的具體應用中將會用到這些元函數。     C++11提供的tuple讓我們編寫模版元程序變得更靈活了,在一定程度上增強了C++的泛型編程能力,下面來看看tuple如何應用於元程序中的。   5.tuple與模版元     C++11的tuple本身就是一個可變模板參數組成的元函數,它的原型如下:   template<class...Types> class tuple;     tuple在模版元編程中的一個應用場景是將可變模板參數保存起來,因為可變模板參數不能直接作為變量保存起來,需要借助tuple保存起來,保存之後再在需要的時候通過一些手段將tuple又轉換為可變模板參數,這個過程有點類似於化學中的“氧化還原反應”。看看下面的例子中,可變模板參數和tuple是如何相互轉換的:     //定義整形序列 template<int...> struct IndexSeq{};   //生成整形序列 template<int N, int... Indexes> struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{};   template<int... indexes> struct MakeIndexes<0, indexes...>{     typedef IndexSeq<indexes...> type; };   template<typename... Args> void printargs(Args... args){     //先將可變模板參數保存到tuple中     print_helper(typename MakeIndexes<sizeof... (Args)>::type(), std::make_tuple(args...)); }   template<int... Indexes, typename... Args> void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){     //再將tuple轉換為可變模板參數,將參數還原回來,再調用print     print(std::get<Indexes>(tup)...);  } template<typename T> void print(T t) {     cout << t << endl; }   template<typename T, typename... Args> void print(T t, Args... args) {     print(t);     print(args...); }     用法:printargs(1, 2.5, “test”); //將輸出1 2.5 test     上面的例子print實際上是輸出可變模板參數的內容,具體做法是先將可變模板參數保存到tuple中,然後再通過元函數MakeIndexes生成一個整形序列,這個整形序列就是IndexSeq<0,1,2>,整形序列代表了tuple中元素的索引,生成整形序列之後再調用print_helper,在print_helper中展開這個整形序列,展開的過程中根據具體的索引從tuple中獲取對應的元素,最終將從tuple中取出來的元素組成一個可變模板參數,從而實現了tuple“還原”為可變模板參數,最終調用print打印可變模板參數。     tuple在模板元編程中的另外一個應用場景是用來實現一些編譯期算法,比如常見的遍歷、查找和合並等算法,實現的思路和可變模板參數實現的編譯期算法類似,關於tuple相關的算法,讀者可以參考筆者在github上的代碼:https://github.com/qicosmos/cosmos/tree/master/tuple。     下面來看看模版元的具體應用。   6.模版元的應用     我們將展示如何通過模版元來實現function_traits和Vairant類型。     function_traits用來獲取函數語義的可調用對象的一些屬性,比如函數類型、返回類型、函數指針類型和參數類型等。下面來看看如何實現function_traits。     template<typename T> struct function_traits;   //普通函數 template<typename Ret, typename... Args> struct function_traits<Ret(Args...)> { public:     enum { arity = sizeof...(Args) };     typedef Ret function_type(Args...);     typedef Ret return_type;     using stl_function_type = std::function<function_type>;     typedef Ret(*pointer)(Args...);       template<size_t I>     struct args     {         static_assert(I < arity, "index is out of range, index must less than sizeof Args");         using type = typename std::tuple_element<I, std::tuple<Args...>>::type;     }; };   //函數指針 template<typename Ret, typename... Args> struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{};   //std::function template <typename Ret, typename... Args> struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{};   //member function #define FUNCTION_TRAITS(...) \     template <typename ReturnType, typename ClassType, typename... Args>\     struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{}; \   FUNCTION_TRAITS() FUNCTION_TRAITS(const) FUNCTION_TRAITS(volatile) FUNCTION_TRAITS(const volatile)   //函數對象 template<typename Callable> struct function_traits : function_traits<decltype(&Callable::operator())>{};     由於可調用對象可能是普通的函數、函數指針、lambda、std::function和成員函數,所以我們需要針對這些類型分別做偏特化。其中,成員函數的偏特化稍微復雜一點,因為涉及到cv符的處理,這裡通過定義一個宏來消除重復的模板類定義。參數類型的獲取我們是借助於tuple,將參數轉換為tuple類型,然後根據索引來獲取對應類型。它的用法比較簡單:     template<typename T> void PrintType() {     cout << typeid(T).name() << endl; } int main() {     std::function<int(int)> f = [](int a){return a; };     PrintType<function_traits<std::function<int(int)>>::function_type>(); //將輸出int __cdecl(int)     PrintType<function_traits<std::function<int(int)>>::args<0>::type>();//將輸出int     PrintType<function_traits<decltype(f)>::function_type>();//將輸出int __cdecl(int) }     有了這個function_traits和前面實現的一些元函數,我們就能方便的實現一個“萬能類型”—Variant,Variant實際上一個泛化的類型,這個Variant和boost.variant的用法類似。boost.variant的基本用法如下:   typedef variant<int,char, double> vt; vt v = 1; v = 'a'; v = 12.32;   這個variant可以接受已經定義的那些類型,看起來有點類似於c#和java中的object類型,實際上variant是擦除了類型,要獲取它的實際類型的時候就稍顯麻煩,需要通過boost.visitor來訪問:       通過C++11模版元實現的Variant將改進值的獲取,將獲取實際值的方式改為內置的,即通過下面的方式來訪問:   typedef Variant<int, double, string, int> cv; cv v = 10; v.Visit([&](double i){cout << i << endl; }, [](short i){cout << i << endl; }, [=](int i){cout << i << endl; },[](const string& i){cout << i << endl; });//結果將輸出10   這種方式更方便直觀。Variant的實現需要借助前文中實現的一些元函數MaxInteger、MaxAlign、Contains和At等等。下面來看看Variant實現的關鍵代碼,完整的代碼請讀者參考筆者在github上的代碼https://github.com/qicosmos/cosmos/blob/master/Varaint.hpp。    View Code   實現Variant首先需要定義一個足夠大的緩沖區用來存放不同的類型的值,這個緩類型沖區實際上就是用來擦除類型,不同的類型都通過placement new在這個緩沖區上創建對象,因為類型長度不同,所以需要考慮內存對齊,C++11剛好提供了內存對齊的緩沖區aligned_storage:   template< std::size_t Len, std::size_t Align = /*default-alignment*/ > struct aligned_storage;   它的第一個參數是緩沖區的長度,第二個參數是緩沖區內存對齊的大小,由於Varaint可以接受多種類型,所以我們需要獲取最大的類型長度,保證緩沖區足夠大,然後還要獲取最大的內存對齊大小,這裡我們通過前面實現的MaxInteger和MaxAlign就可以了,Varaint中內存對齊的緩沖區定義如下:   enum {     data_size = IntegerMax<sizeof(Types)...>::value,     align_size = MaxAlign<Types...>::value }; using data_t = typename std::aligned_storage<data_size, align_size>::type; //內存對齊的緩沖區類型   其次,我們還要實現對緩沖區的構造、拷貝、析構和移動,因為Variant重新賦值的時候需要將緩沖區中原來的類型析構掉,拷貝構造和移動構造時則需要拷貝和移動。這裡以析構為例,我們需要根據當前的type_index來遍歷Variant的所有類型,找到對應的類型然後調用該類型的析構函數。     void Destroy(const type_index& index, void * buf)     {         std::initializer_list<int>{(Destroy0<Types>(index, buf), 0)...};     }       template<typename T>     void Destroy0(const type_index& id, void* data)     {         if (id == type_index(typeid(T)))             reinterpret_cast<T*>(data)->~T();     }     這裡,我們通過初始化列表和逗號表達式來展開可變模板參數,在展開的過程中查找對應的類型,如果找到了則析構。在Variant構造時還需要注意一個細節是,Variant不能接受沒有預先定義的類型,所以在構造Variant時,需要限定類型必須在預定義的類型范圍當中,這裡通過type_traits的enable_if來限定模板參數的類型。     template <class T,     class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type> Variant(T&& value) : m_typeIndex(typeid(void)){             Destroy(m_typeIndex, &m_data);             typedef typename std::remove_reference<T>::type U;             new(&m_data) U(std::forward<T>(value));             m_typeIndex = type_index(typeid(U));     }     這裡enbale_if的條件就是前面實現的元函數Contains的值,當沒有在預定義的類型中找到對應的類型時,即Contains返回false時,編譯期會報一個編譯錯誤。     最後還需要實現內置的Vistit功能,Visit的實現需要先通過定義一系列的訪問函數,然後再遍歷這些函數,遍歷過程中,判斷函數的第一個參數類型的type_index是否與當前的type_index相同,如果相同則獲取當前類型的值。     template<typename F>     void Visit(F&& f){         using T = typename Function_Traits<F>::template arg<0>::type;         if (Is<T>())             f(Get<T>());     }       template<typename F, typename... Rest>     void Visit(F&& f, Rest&&... rest){         using T = typename Function_Traits<F>::template arg<0>::type;         if (Is<T>())             Visit(std::forward<F>(f));         else             Visit(std::forward<Rest>(rest)...);     }     Visit功能的實現利用了可變模板參數和function_traits,通過可變模板參數來遍歷一系列的訪問函數,遍歷過程中,通過function_traits來獲取第一個參數的類型,和Variant當前的type_index相同的則取值。為什麼要獲取訪問函數第一個參數的類型呢?因為Variant的值是唯一的,只有一個值,所以獲取的訪問函數的第一個參數的類型就是Variant中存儲的對象的實際類型。   7總結     C++11中的一些特性比如type_traits、可變模板參數和tuple讓模版元編程變得更簡單也更強大,模版元編程雖然功能強大,但也比較復雜,要用好模版元,需要我們轉變思維方式,在掌握基本的理論的基礎上,再認真揣摩模版元的一些常用技巧,這些技巧是有規律可循的,基本上都是通過重定義、遞歸和偏特化等手法來實現的,當我們對這些基本技巧很熟悉的時候再結合不斷地實踐,相信對模版元編程就能做到“游刃有余”了。    
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved