Created
December 27, 2021 06:35
-
-
Save riseOfCurse/195583bdb903aa01ffef7d278b10771c to your computer and use it in GitHub Desktop.
cpptemplatetutorial
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## 4 元编程下的数据结构与算法 | |
### 4.1 表达式与数值计算 | |
#### 4.1.1 const与常量表达式 | |
我们先从大家都熟悉的const开始来介绍常量表达式,总所周知const是用于修饰一个常量的,我们声明一个普通的const 常量就必须给予初始化。 | |
而所谓的常量表达式,就是定义能在编译时求值的表达式。 | |
比如: | |
```C++ | |
//为了方便期间,我们用一个简单的类模板来观察一个表达式是否是常量表达式 | |
template<int x> | |
struct check{}; | |
const int MIN_SIZE = 996; check<MIN_SIZE>{}; | |
//OK, 且MAX_SIZE是常量表达式,check<MIN_SIZE>{}是通过编译 | |
const int MIN_SIZE_2 = MIN_SIZE + 1; check<MIN_SIZE_2>{}; | |
//OK, MIN_SIZE_2也是常量表达式,也在编译期就已经确定 | |
const int FOO = return_10(); //这个是OK的 | |
check<FOO>{}; //Error, FOO并非常量表达式,FOO在运行期确定。 | |
/*假设我们的函数是这样的 | |
int return_10(){ | |
return 10; | |
} | |
*/ | |
``` | |
函数的返回值是可以赋值给常量的,但是看起来如此简单的函数赋值给const常量,很明显一看就是可以在编译期间就可以得到结果的,但是实际上它变成了一个read only的变量(运行期),实际中我们可能会遇到一些传进来是常量表达式,而且输出也是简单固定的,那么把这些计算优化到编译期去做,显然会让程序变得稍微快那么一些,能让编译器去这么做的关键字在下面会介绍到。 | |
此外,在类(class、struct)中,我们可以用以下几种形式: | |
```C++ | |
//Referenced from: https://zh.cppreference.com/w/cpp/language/static | |
struct X | |
{ | |
static const int n = 1; | |
static const int m{2}; // C++11 起 | |
static const int k; | |
//static const int arr[] = { 4, 5, 6 }; 出错,static const 不可以初始化复杂的类型 | |
const int arr[3] = { 4, 5, 6 }; //但是类中可以定义const int arr[3] = { 4, 5, 6 }; | |
X(){ | |
//check<arr[3]>{}; 出错,this指针不能再常量表达式中使用 | |
} | |
}; | |
const int X::k = 3; | |
//在main函数中的测试: | |
check<X::n>{}; //OK | |
check<X::m>{}; //OK | |
check<X::k>{}; //OK | |
``` | |
static const 在类中并不支持初始化复杂的类型,但是我们明眼人仍旧可以看出这里的`static const int arr[] = { 4, 5, 6 }`也完全是可以在编译期就搞定的东西。甚至像是`static const std::array<int, 4> arr2;` 这样的也明显可以是在编译期就搞定的,那么就引出我们的新生代主角constexpr | |
#### 4.1.2 constexpr与常量表达式 | |
从C++11标准起,引入了一个关键字叫做constexpr,顾名思义,就是const expression,就是常量表达式。 | |
constexpr可以修饰函数,变量等等(不可以修饰函数的参数),对于被声明为constexpr的变量或者函数,则它们在编译期间被求值是**可能的**(也就是也可以不能) | |
现在我们来思考这样一个情景,假设我们用常量表达式定义了一个编译期整数常量`constexpr int bleem = 34;`据说这是一个非常强大的数,我们定义了这个数,通过一系列很nb轰轰的函数就可以得到另一个更为强大的整数,通过这个整数我们可以爆炸了。好了,我们要定义一个新的数如果仅仅使用手写会很不好看,显然定义成函数调用的形式就非常合适,那么就是`constexpr double boom = add42(div10(bleem))`; | |
```C++ | |
constexpr double div10(const int x){ | |
return static_cast<double>(x)/10.0; | |
} | |
constexpr int add42(const double x){ | |
return static_cast<int>((x + 42)*10); | |
} | |
int main(void){ | |
constexpr int bleem = 34; | |
constexpr int boom = add42(div10(bleem)); | |
check<boom>{}; | |
} | |
``` | |
没有问题,我们成功得到了可以爆炸的数。我们可以看到,当传入的实参和函数都是常量表达式时,我们的嵌套调用的计算函数确实是可以在编译期进行运算的。 | |
另外我们需要注意的一点是,constexpr的函数是需要非常简单的,而且还需要的一些其他要求,如果要做到constexpr函数在编译期运算。需要满足很多条件: | |
其中最显然的有: | |
* 其返回类型(若存在)必须是字面类型 (LiteralType) | |
* 其每个参数都必须是字面类型 (LiteralType) | |
另外因为我们的constexpr函数的参数没法指定为constexpr,所以我们无法写像下面这样的代码 | |
```C++ | |
constexpr double div10(const int x){ | |
constexpr int xcopy = x; //x并没有被声明为constexpr常量 | |
return x/10; | |
} | |
``` | |
更多详细的条例可以在这里找到参考 [constexpr 说明符](https://zh.cppreference.com/w/cpp/language/constexpr) | |
前面提到过,在类里面,static const 不可以初始化复杂的类型 ,但是constexpr可以: | |
```C++ | |
//Referenced from: https://zh.cppreference.com/w/cpp/language/static | |
struct Y { | |
static constexpr int arr[] = { 1, 2, 3 }; // OK | |
static constexpr std::complex<double> n = {1,2}; // OK | |
static constexpr int k; // 错误:constexpr static 要求初始化器 | |
}; | |
``` | |
有时候我们看着const和constexpr是有点类似的东西,例如上面的X与Y两个类,constexpr可以初始化的对象比const要复杂一点,而编译器还允许我们同时使用constexpr和const,比如`constexpr const N = 999;`,但是在这里constexpr包含了const的语义,在这里我们可以写成`constexpr N = 999` 或者 `const N = 999`,从c++11起,constexpr对于类成员函数也包含了const的语义,即不改变类的成员对象。 | |
还要一点需要注意的是,对于constexpr函数,虽然我们显式地写出来告诉编译期这个函数是可以在编译期计算的,但是它也可以不是在编译期运行的函数。比如我们之前所写的函数 | |
```C++ | |
constexpr double div10(const int x){ | |
return static_cast<double>(x)/10.0; | |
} | |
constexpr int add42(const double x){ | |
return static_cast<int>((x + 42)*10); | |
} | |
int i; | |
cin >> i; | |
div10(i); //我们可以当成普通的运行期函数调用,但是返回值已经不再是编译期常量了 | |
``` | |
从C++17开始,constexpr也可以用于lambda表达式了,而且如果你不显式地把constexpr写上而lambda函数的写法完全符合constexpr函数时,它也会是一个constexpr函数。 | |
#### 4.1.3 enum与常量表达式 | |
在C++中,enum中的值也是编译期常量, | |
```C++ | |
enum Test{ | |
Num1 | |
}; | |
check<Test(Num1)>{}; //OK | |
``` | |
其中的`Num1`也可以写成是这个enum里其他项的常量计算表达式,比如 | |
```C++ | |
enum Test{ | |
Num1 = 1, Num2 = 4, Num3 = Num1 + Num2 | |
}; | |
``` | |
#### 4.1.4 consteval与常量表达式 | |
C++20开始,我们有了一个新的说明符consteval,consteval - 指定函数是立即函数(immediate function),即每次调用该函数必须产生编译时常量。(Referenced from cppreference) | |
constexpr还有编译期和运行期的两面性,但consteval则完全都是编译期了。 | |
#### 4.1.5 百玩不厌的老梗:第n个斐波那契数 | |
我们知道,在C++中enum也是编译期常量,并且还可以嵌在类里面。于是这样就可以利用模板的int类型参数来做数学计算。 | |
```C++ | |
template<unsigned int x> | |
struct fib{ | |
enum{ | |
result = fib<x-1>::result + fib<x-2>::result | |
}; | |
}; | |
template<> | |
struct fib<2>{ | |
enum{ | |
result = 1 | |
}; | |
}; | |
template<> | |
struct fib<1>{ | |
enum{ | |
result = 1 | |
}; | |
}; | |
``` | |
这样的函数是用一个声明+两个全特化来完成递归调用的。当我们求`fib<5>::result`的时候,我们求了`fib<4>`和`fib<3>`,这样的树形调用会生成非常多`fib<>`的模板,所以我们可以使用尾递归的写法如下: | |
尾递归: | |
```C++ | |
template<unsigned int a, unsigned int b, unsigned int count> | |
struct fib_iter{ | |
enum{ | |
result = fib_iter<a+b, a, count-1>::result | |
}; | |
}; | |
template<unsigned int a, unsigned int b> | |
struct fib_iter<a, b, 0>{ | |
enum{ | |
result = b | |
}; | |
}; | |
template<unsigned int n> | |
struct fib{ | |
enum{ | |
result = fib_iter<1, 0, n>::result | |
}; | |
}; | |
``` | |
除了enum以外,我们也可以使用 `static const int result `或者 `static constexpr int result `,//TODO 区别 | |
从c++17起,引入了constexpr if,所以除了利用类模板,我们也可以用constexpr来写编译期的斐波那契函数(函数模板): | |
```C++ | |
template<unsigned int x> | |
constexpr int fib(){ | |
if constexpr(x <= 2){ | |
return 1; | |
} else { | |
return fib<x-1>() + fib<x-2>(); | |
} | |
} | |
``` | |
当然,不用模板和constexpr if,我们还可以利用三目运算符来写fib函数: | |
```C++ | |
constexpr int fib(unsigned int n) { | |
return n <= 2 ? 1 : fib(n - 1) + fib(n - 2); | |
} | |
``` | |
### 4.2 获得类型的属性——类型萃取(Type Traits) | |
#### 4.2.1 头文件<type_traits> | |
在<type_traits>中有一系列的“谓词”可以使用,比如我们可以利用`is_same`来判断两个类是否是同一个类,比如我们`using myint = int`,然后再来使用`is_same<myint, int>::value`,这个结果就是1,也就是这两个类型是相同的。在`if constexpr()`的括号中,必须是编译期常量,因此我们除了可以把模板中的数值类型放入以外,我们也可以使用type_traits中给出了工具。 | |
<type_traits>中的“谓词”都是继承了其中的辅助类`template<typename T, T v> struct integral_constant`,利用这个,头文件中定义了两个类型:`true_type`和`false_type`,他们的`::value`分别是true和false,然后其他的各种类模板判断,都可以利用它们的各自的特化来继承这两个类,所以可想而知,`is_same<myint, int>`肯定继承了`true_type`,而`is_same<myint, char>`肯定继承了false_type。所以我们也可以增加自己的type traits, 配合enable_if,我们就可以对模板中的参数进行类型上的限定了。在4.3小节中我们可以看到这样的用法。 | |
除此以外,还有前几章提到过的,我们可以对一个传入的类型进行[添加/删除] [引用/指针]的操作,我们甚至还可以添加或移除const,volatile等说明符。 | |
具体的所有工具可以参考cppreference。 | |
#### 4.2.2 头文件<concepts> | |
从c++20开始,我们可以使用concept和require来取代enable_if了,concept本质和头文件<type_traits>中大部分类模板的功能类似。都是做模板上的类型判断,判断某些参数有哪些属性。比如上文中 提到过的`is_same`这个type traits,我们的<concepts>中的`same_as`这个concept就是在`is_same`的基础上写的。在4.3小节中,我们也会尝试使用concept和require。用g++编译,需要带上参数`-std=c++2a -fconcepts` | |
### 4.3 列表与数组 | |
#### 4.3.1 再看List | |
首先我们来看一个Haskell中List的例子: | |
```Haskell | |
data MyList a = Nil | Cons a (MyList a) | |
``` | |
那么根据同样思路,我们可以写成这样: | |
```C++ | |
//首先我们定义一个Nil | |
struct Nil{}; | |
//定义Cons: | |
template<typename Head, typename Rest> | |
struct Cons{}; | |
template<typename T> | |
struct Cons<T, Nil>{}; //别忘了这个 | |
``` | |
我们可以用`typeid(Cons<Nil, Cons<Nil, Nil> >{}).name() //(gcc需要使用abi::__cxa_demangle(...))`来看一下我们得到了什么:`Cons<Nil, Cons<Nil, Nil> >`就是这个,没问题。 | |
我们知道C++ template是支持可变模板参数的,所以我们还可以定义一个List,然后把List转换成Cons结构。 | |
```C++ | |
template<typename head, typename ...tail> | |
struct make_list { | |
using result = Cons<head, typename make_list<tail...>::result>; | |
}; | |
template<typename head> | |
struct make_list<head> { | |
using result = Cons<head, Nil>; | |
}; | |
template<typename ...arg> | |
using List = typename make_list<arg...>::result; | |
``` | |
咦?那么既然支持形参包,那么我们完全可以不用Cons结构,直接造出一个List来: | |
```C++ | |
template<typename...> | |
struct List{}; | |
``` | |
干干净净,甚至什么都没有。虽然`Cons`结构在函数式编程语言里是更为"核心"的存在,但是我们没有必要照本宣科,如此定义的List简洁好看,也一样方便使用,而且我们还可以反过来,把`Cons`结构变成我们的`List`,上面的`make_list`这样看起来恶心的一坨就没有必要存在了。 | |
首先我们需要定义一个concat函数来合并两个List: | |
```C++ | |
template<typename, typename> | |
struct concat{}; | |
template<typename ...items1, typename ...items2> | |
struct concat<List<items1...>, List<items2...>>{ | |
using result = List<items1..., items2...>; | |
}; | |
template<typename list1, typename list2> | |
using concat_r = typename concat<list1, list2>::result; | |
``` | |
当我们编写这种“函数”的时候,便于思考的形式就是先写下函数的原型,比如`template<typename list, typename list2> | |
struct concat{};`先声明了参数是两个List,然后具体的函数是怎么操作的,我们再利用特化和偏特化来处理。下面的写法大多都是基于这种形式的。 | |
现在我们拥有了这个无比强大的函数,我们就可以把`Cons<...>`转化为List了: | |
```C++ | |
template<typename head, typename rest> | |
struct Cons{ | |
using toList = typename concat< List<head>, typename rest::toList> :: result; | |
}; | |
template<typename head> | |
struct Cons<head, Nil>{ | |
using toList = List<head>; | |
}; | |
``` | |
这么写有点不好看,我们在Cons里面挖了一个洞,这里的toList可以看做一个函数,这样还颇有一种面向对象的味道。当然我们也可以保持Cons的纯洁性(即不写上面那种),而去增加一个函数进行转化: | |
```C++ | |
template<typename> | |
struct listfromCons{}; | |
template<typename head, typename rest> | |
struct listfromCons<Cons<head, rest>>{ | |
using result = typename concat< | |
List<head>, | |
typename listfromCons<rest>::result | |
>::result; | |
}; | |
template<typename head> | |
struct listfromCons<Cons<head, Nil>>{ | |
using result = List<head>; | |
}; | |
template<typename T> | |
using listfromCons_r = typename listfromCons<T>::result; | |
``` | |
为了方便我们的测试,我们可以定义一个数值类型Int: | |
```C++ | |
template<int> | |
struct Int{}; | |
``` | |
我们尝试运行`listfromCons_r<Cons<Int<1>, Cons<Int<1>, Cons<Int<2>, Nil> > > >`,得到了`List<Int<1>, Int<1>, Int<2> >`,尝试`Cons<Int<1>, Cons<Int<1>, Cons<Int<2>, Nil> > >::toList`,也得到了一样的值。 | |
然后反过来也是一样的: | |
```C++ | |
template<typename> | |
struct consfromList{}; | |
template<typename head, typename...rest> | |
struct consfromList<List<head, rest...>>{ | |
using result = Cons< | |
head, typename consfromList<List<rest...> >::result | |
>; | |
}; | |
template<typename tail> | |
struct consfromList<List<tail>>{ | |
using result = Cons<tail, Nil>; | |
}; | |
template<typename T> | |
using consfromList_r = typename consfromList<T>::result; | |
``` | |
`consfromList_r<List<Int<123>, Int<32>, Int<1> > >` 变成了 | |
`Cons<Int<123>, Cons<Int<32>, Cons<Int<1>, Nil> > >` | |
#### 4.3.2 也是Tuple?List上的类型限定 | |
我们思考一下Tuple会写成什么样子,我们的Tuple里的类型显然是“泛型”的,也就是什么类型都可以往里面塞,也肯定是支持可变长参数的,所以 我们会写成。。。 | |
```C++ | |
template<typename...> | |
struct Tuple{}; | |
``` | |
让我们仔细再观察一下List的写法 | |
```C++ | |
template<typename...> | |
struct List{}; | |
``` | |
emmm,变得和List完全一样了。我们发现这里的List是类似于Python里的list一样,我们可以传入任何类型的参数,比如我们再构造一个Char类型的数据 | |
```C++ | |
template<char> | |
struct Char{}; | |
``` | |
然后我们可以这么写List:`List<Int<1>, Char<'c'> >` | |
甚至可以做List的嵌套:`List<Int<1>, List<Int<1>, Int<2>>>` | |
那么我们是否可以对List里的类型做任何限定呢?我们需要List里的类型都是一样的,这样的List可以做到吗? | |
这里我们需要引入template template parameter这个东西了,我们先思考,如何判断多个个`Int<?>`是一样的, 即我们需要这样的一个可变长参数的的判断函数: | |
``` | |
is_all_same_type<Int<1>, Int<2>, Int<3>> ==> std::integral_constant<bool, true> | |
is_all_same_type<Int<1>, Char<'c'>> ==> std::integral_constant<bool, false> | |
``` | |
我们可以用`is_same`吗,很遗憾,不可以,对于`is_same`来说,`Int<1>, Int<2>`是两个不一样的类型,那么怎么构造我们的`is_all_same_type`呢? | |
别那么着急,我们先一步一步来,既然我们要做到这一点,我们先从简单的两个类型开始,我们先定义一个`is_same_template_name`,因为很显然,`Int<1>`和`Int<2>`相同的点显然在这个`Int`。 | |
```C++ | |
template<template<typename...> typename T, template<typename...> typename U> | |
struct is_same_template_name : std::false_type{}; | |
template<template<typename...> typename T> | |
struct is_same_template_name<T, T> : std::true_type{}; | |
``` | |
我们看看,`is_same_template_name`接受的参数是什么呢?显然是一个“带模板的模板”,所以我们的`is_same_template_name`可以这么调用 | |
`is_same_template_name<Int, Char>`。但是这里有一个问题,我们这个接受的模板,它本身的参数是typename,不能是int和char啊,那么我们真的可以这么调用吗?显然不行,因为我们注意到,此时的`Int<?>` 中的 ? 是 int类型的,那么显然我们需要重写我们的基本类型: | |
```C++ | |
template<int> | |
struct _Int_{}; | |
template<typename> | |
struct Int{}; | |
template<char> | |
struct _Char_{}; | |
template<typename> | |
struct Char{}; | |
``` | |
这样我们把`Int`和`Char`的参数也变成了typename,可以说非常繁琐了,但是得到的效果是可喜可贺的,我们现在看看`is_same_template_name<Int, Char>::type`,我们得到了`std::integral_constant<bool, false>`。 | |
接下来我们尝试写出这个`is_all_same_type`: | |
```C++ | |
template<typename...T> | |
struct is_all_same_type : std::false_type{}; | |
template< | |
template<typename...t> typename T, typename...t, | |
template<typename...u> typename U, typename...u | |
> | |
struct is_all_same_type<T<t...>, U<u...>> : is_same_template_name<T, U>{}; | |
template<typename T1, typename T2, typename...R> | |
struct is_all_same_type<T1, T2, R...> : is_all_same_type<T2, R...>{}; //这里可以用继承来获得{true/false}_type | |
``` | |
我们来测试一下: | |
```C++ | |
is_all_same_type<Int<_Int_<1>>, Char<_Char_<'c'>>>::type | |
==> std::integral_constant<bool, false> | |
is_all_same_type<Int<_Int_<1>>, Int<_Int_<2>>, Int<_Int_<3>>>::type | |
==> std::integral_constant<bool, true> | |
is_all_same_type< | |
List<Int<_Int_<1>>>, List<Char<_Char_<'a'>>> | |
>::type | |
==> std::integral_constant<bool, true> | |
``` | |
觉得`Int<_Int_<2>>`太繁琐了吧,我们可以用宏来遮掩一下丑陋: | |
```C++ | |
#define Int(n) Int<_Int_<n>> | |
#define Char(n) Char<_Char_<n>> | |
``` | |
然后我们就可以定义我们的严格列表了: | |
```C++ | |
template<typename...Ts> | |
struct StrictList : List<std::enable_if_t<is_all_same_type<Ts...>::value>>{}; | |
``` | |
我们尝试使用`StrictList<Int(1), Char(2)>` No,报错了,不可以,而` StrictList<Int(1), Int(2)>{}` 可以 | |
在我们写的这个`is_all_same_type`的基础上,我们可以写一个concept+require的版本: | |
```C++ | |
template<typename...Ts> | |
concept SameType = is_all_same_type<Ts...>::value; | |
template<typename...Ts> | |
requires SameType<Ts...> | |
struct StrictList : List<Ts...>{}; | |
``` | |
#### 4.3.3 其他方便我们构造List的函数 | |
比如append函数和prepend函数,把一个东西插入一个List中: | |
```C++ | |
template<typename list, typename item> | |
struct append{}; | |
template<typename...items, typename new_item> | |
struct append<List<items...>, new_item>{ | |
using result = List<items..., new_item>; | |
}; | |
template<typename list, typename item> | |
struct prepend{}; | |
template<typename...items, typename new_item> | |
struct prepend<List<items...>, new_item>{ | |
using result = List<new_item, items...>; | |
}; | |
``` | |
其中`append`是尾插,`prepend`是头插,两者除了result那里以外其他地方基本相同。 | |
接下来我们实现`filter`这个函数,这里我们需要做一些铺垫了,首先实现filter不像之前写的函数那样,它需要接受一个“函数”的传入,再用List中 的元素挨个用这个函数执行,通过才可以加入新的list,这里面隐含的一个`if_else`的逻辑如果真的使用特化去搞模式匹配,会显得很麻烦,不如我们直接构造一个`if_else`来帮助我们写这个函数。 | |
首先我们可以增加一个`Bool`类型的包裹。和之前提到过的`Int`一样,因为我们StrictList的需求,所以需要两次包裹才能正确判断template-name是否相同。为了可以使用`StrictList<Bool(true), Bool(false)>`,需要搞得复杂一点,需要写成`StrictList<Bool(_Bool_(true)), Bool(_Bool_(false))>`,但是这里我们不想这么麻烦,我们使用一次Bool类型的包裹就可以了 | |
```C++ | |
template<bool> | |
struct Bool{}; | |
template<class cond, class t, class f> | |
struct if_else{}; | |
template<class t, class f> | |
struct if_else<Bool<true>, t, f>{ | |
using result = t; | |
}; | |
template<class t, class f> | |
struct if_else<Bool<false>, t, f>{ | |
using result = f; | |
}; | |
``` | |
这样我们就完成了ifelse控制结构的定义了,在这里t和f可以是任何东西,可以是模板数据结构,也可以是模板“函数”,借此我们也可以定义三个判断整数大小等的函数: | |
```C++ | |
template<typename...arg> | |
struct intLarger{}; | |
template<int a, int b> | |
struct intLarger<Int<_Int_<a>>, Int<_Int_<b>>>{ | |
using result = typename if_else<Bool<bool(a>b)>, Bool<true>, Bool<false> > ::result; | |
}; | |
template<typename...arg> | |
struct intEqual{}; | |
template<int a, int b> | |
struct intEqual<Int<_Int_<a>>, Int<_Int_<b>>>{ | |
using result = typename if_else<Bool<bool(a==b)>, Bool<true>, Bool<false> > ::result; | |
}; | |
template<typename...arg> | |
struct intSmaller{}; | |
template<int a, int b> | |
struct intSmaller<Int<_Int_<a>>, Int<_Int_<b>>>{ | |
using result = typename if_else<Bool<bool(a<b)>, Bool<true>, Bool<false> > ::result; | |
}; | |
``` | |
`intLarger`,`intEqual`,`intSmaller`都是函数,那么细心的读者可能会看到这里的函数声明非常奇怪,正常来说我们十分确定这三个函数只有2个参数,那么为什么我们要写成`<typename...arg>`呢?这里其实蕴含了柯里化的思想(currying),我们希望我们的过滤函数在filter中可以用的很灵活,比如这么使用:`filter<intLarger<Int(5)>, List<Int(1), Int(8), Int(6)>>::result => List<Int(1)>`那么可以符合这种写法的方法就是使用形参包的形式,因为固定参数的类模板在使用时需要传入的参数也必须是固定的,我们通过形参包实现了currying的调用方式(不过使用的时候并不安全,需要人为去记住),当然事实上我们可以给所有函数安排currying,不过在这里被高阶函数调用非常实用,所以我们在这里写。 | |
这样理清楚了思路,我们就可以着手实现我们的filter函数了: | |
```C++ | |
template<typename fn, typename list> | |
struct filter{}; | |
template<template<typename...> typename fn, typename...args, typename x, typename...rest> | |
struct filter<fn<args...>, List<x, rest...>>{ | |
using result = typename if_else< typename fn<x,args...> ::result, | |
typename prepend<typename filter<fn<args...>, List<rest...>> ::result, x> ::result , | |
typename filter<fn<args...>, List<rest...>>::result > ::result; | |
}; | |
template<typename fn> | |
struct filter<fn, List<>>{ | |
using result = List<>; | |
}; | |
``` | |
我们测试测试`filter<intLarger<Int(5)>, List<Int(1),Int(6)>>::result` 结果真的是`List<Int(6)>` | |
### 4.4 字典结构 | |
### 4.5 “快速”排序 | |
当我们有了filter函数之后,我们就可以很容易地写出haskell风格的快速排序了: | |
```C++ | |
template<typename list> | |
struct quickSort{}; | |
template<typename x, typename...rest> | |
struct quickSort<List<x, rest...>>{ | |
using result = | |
typename concatAll< | |
typename quickSort< typename filter<intSmaller<x>, List<rest...>>::result>::result, | |
List<x>, typename quickSort< typename filter<intEqual<x>, List<rest...>>::result>::result, | |
typename quickSort< typename filter<intLarger<x>, List<rest...>>::result>::result | |
>::result; | |
}; | |
template<> | |
struct quickSort<List<>>{ | |
using result = List<>; | |
}; | |
``` | |
这里的concatAll各位读者可以自己利用concat来实现,即把concat扩展到对1一个列表以上全部都连接起来 | |
我们测试`quickSort< | |
List<Int(1),Int(9),Int(7),Int(8),Int(2),Int(4),Int(6),Int(5)> | |
>::result{}` | |
真的变成了:`List<Int<_Int_<1> >, Int<_Int_<2> >, Int<_Int_<4> >, Int<_Int_<5> >, Int<_Int_<6> >, Int<_Int_<7> >, Int<_Int_<8> >, Int<_Int_<9> > >` | |
### 4.6 其它常用的“轮子” | |
## 5 模板的进阶技巧 | |
### 5.1 嵌入类 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment