模板的数值计算发生在编译期,计算的参数由模板入参输入,计算的结果由模板内部定义的enum或者static const的整形成员保存。
如下我们实现编译期的整数加法:
template<int X, int Y>
struct Plus
{
enum { Value = X + Y };
};
可以如下面的方式使用类模板Plus
:
int main()
{
std::cout << Plus<3, 4>::Value << std::endl;
return 0;
}
上面代码会打印出7。注意这里对7的计算发生在编译期,当我们在main函数中打印的时候结果其实已经是计算好了的。
为了验证我们的说法,我们想办法让结果在C++的编译过程中就打印出来。
template<int Value>
struct Print
{
operator char()
{
return Value + 0xFF; // INVOKE OVERFLOW WARNNING LOG !
}
};
#define __print(token, ...) char print_value_##tocken = Print<__VA_ARGS__>()
由于C++的编译期计算无法直接操作IO,我们只能想办法让C++编译器将计算结果在编译信息中打印出来。如上__print
宏定义中将一个Print<Value>
的对象赋值给一个临时的char变量,这时调用了Print中定义的char类型隐式转换函数operator char()
。该隐式转换函数中将Value和0xFF进行相加,这将会导致编译器会产生一个char类型值溢出告警,从而将Value的值打印出来。
这时我们可以删除main函数,然后在任一cpp文件中包含上述Plus和Print模板的头文件,然后写下如下语句
// TestPrint.cpp
// should include the header files of Plus and Print
__print(Plus_3_4, Plus<3, 4>::Value);
在gcc4.8.4上,编译器编译到该文件时打印如下:
TestPrint.cpp: In instantiation of 'Print<Value>::operator char()[with int value = 7]':
TestPrint.cpp: required form here
TestPrint.cpp: warning: overflow in implicit constant conversion [-Woverflow]
可以看到计算结果是在编译告警Print<Value>::operator char()[with int value = 7]
中打印出来的。
由于一个文件中__print
可能会被使用多次,所以它的第一个参数token是为了让每个临时的char变量的名字不重复。我们可以优化__print
的定义,避免让用户每次传递一个不同的token,而让编译器帮助我们来做这件事。如下:
#define __print(...) char UNIQUE_NAME(print_value_) = Print<__VA_ARGS__>()
我们使用了UNIQUE_NAME
宏来为临时的char变量产生不重复的名字,UNIQUE_NAME
的定义如下。这里使用了一个小技巧,将当前行号拼接到一个固定的token中产生了一个不重复的名字。
#define __DO_JOIN(symbol1, symbol2) symbol1##symbol2
#define _JOIN(symbol1, symbol2) __DO_JOIN(symbol1, symbol2)
# define UNIQUE_NAME(prefix) _JOIN(prefix, __LINE__)
现在我们可以这样使用__print
了:__print(Plus<3, 4>::Value);
。
对于类模板Plus我们可以这样理解:它就如同一个函数,Plus
是函数名,它声明需要两个int型的入参,分别是形参X
和Y
。它的返回值是内部定义的Value
。对它的调用方式是Plus<3, 4>::Value
,尖括号中传递实参,通过调用Value
获得计算结果。只不过这个函数的计算发生在C++编译期。可以认为C++编译期函数为了和运行期函数进行区分,选择使用了尖括号而非圆括号。
C++这种编译期的函数其实是支持多返回值的,例如我们修改上例的代码,一次计算出整数加减乘除的所有结果。
template<int X, int Y>
struct Caculate
{
enum
{
Sum = X + Y,
Dec = X - Y,
Mul = X * Y,
Div = X / Y
};
};
可以使用Caculate<10, 2>::Sum
,Caculate<10, 2>::Dec
,Caculate<10, 2>::Mul
,Caculate<10, 2>::Div
分别获得10和2的加减乘除的结果。
这种多返回值计算在我们后续介绍元编程时使用的并不多。原因之一是当我们只想要其中一个计算结果时,所有的结果其实是一起被计算了,会存在额外的计算开销。另外一个重要的原因是,多个返回值必须每个的名字都不一样,这会对编译期的函数组合能力造成障碍。
除了用enum保存编译期的数值计算结果,还可以用 static const成员变量。上面例子修改如下效果一样。
template<int X, int Y>
struct Plus
{
static const int Value = X + Y;
};
由于有些编译器对static const在模板的计算中存在问题,需要为其分配内存,所以本文对于编译期数值计算结果的保存一律使用enum。