C++11 模板元编程 - 元函数


我们继续演进前面那个无聊的类型计算的例子,来得出元函数的定义。

前面我们实现了PointerOf,它对于传进的任意类型T可以计算出T的指针类型。

template
struct PointerOf
{
    using Result = T*;
};

现在我们想要实现一个能够计算T的指针的指针类型的模板,怎么做?

一种做法是直接定义一个新的模板:

template
struct Pointer2Of
{
    using Result = T**;
};

为了让类型计算结果更像是出自函数的返回值,我们将计算结果的类型别名后续统一叫做Result。上述类模板本质上是一个对类型进行计算的函数:

Pointer2Of :: (typename T) => T -> T**

可以这样使用该函数:

int* pi;
Pointer2Of::Result ppi = &pi

上述代码中Pointer2Of::Result的计算发生在编译期,当在C++运行期前它已经得到计算结果int**了。所以上述代码在编译器计算完成后,就相当于如下代码:

int* pi;
int** ppi = &pi

虽然我们把类模板当做编译期函数来看,但是这种函数语法看起来和我们熟悉的函数相差较大,但究其本质和函数调用并无差异,都是为函数传入符合要求的实参,获得函数返回结果。

我们可以认为由于圆括号已经优先给了运行时C++函数,所以这种编译期C++函数的定义和调用都使用尖括号,并且需要显示调用Result才对函数进行运算求值。当使用这种编译期函数但并不调用Result时,和在“运行期C++”中使用一个函数指针类似,仅用做保存和传递用,但并不求值。

编译期函数计算,可以调用已有的其它编译期函数。如下通过嵌套调用PointerOf,也可以实现Pointer2Of:

template
struct Pointer2Of
{
    using Result = typename PointerOf::Result>::Result;
};

上面我们通过嵌套调用两次PointerOf来完成Pointer2Of的实现。在Pointer2Of中我们每次使用PointerOf<...>::Result时前面都用了typename关键字。原因是一旦PointerOf后面的尖括号中存在非具体类型的话,那么PointerOf的内部类型Result就是一个推导类型。C++标准要求使用推导类型前面必须使用typename关键字显示指明这是一个类型。所以我们在Pointer2Of中使用PointerOf完整的方式是这样的:typename PointerOf<...>::Result

和Haskell相比,我们必须得承认C++的这种函数式编程的书写确实太繁琐了。为了简化对元函数的使用,我们可以用宏封装一下PointerOf:

#define __pointer(...) typename PointerOf<__VA_ARGS__>::Result

这样Pointer2Of的定义可以简化如下:

template
struct Pointer2Of
{
    using Result = __pointer(__pointer(T));
};

现在看起来好多了,__pointer(T)的写法更像是在调用一个函数。

可以看到我们对类模板进行约束,固定用Result保存计算结果,且只返回单一结果,可以使我们将模板当做函数使用时的写法得到统一,这对于我们进行函数组合简直是必须的。

后续我们将一直把这种在编译期进行计算,靠Result返回计算结果的类模板看作是编译期的函数,它的目的是为了支持C++模板元编程。为了和C++运行时函数进行区分,后文中我们统一将其称作元函数

如同函数是函数式编程的构成基础一样,元函数是C++模板元编程的构成基础。


高阶函数

返回 C++11模板元编程 - 目录

你可能感兴趣的:(C++11 模板元编程 - 元函数)