C++templates第八章编译期编程

C++一直以来都包含一些可以被用来进行编译器计算的简单方法。模板则进一步增加了编译器计算的可能性,而且该语言进一步的发展通常也都是在这一工具箱里进行的。
比较简单的情况是,可以通过它来决定是否启用某个模板,或者在多个模板之间做选择。不 过如果有足够多的信息,编译器甚至可以计算控制流的结果。

模板元编程

模板的实例化发生在编译期间(而动态语言的泛型是在程序运行期间决定的)。事实证明 C++模板的某些特性可以和实例化过程相结合,这样就产生了一种 C++自己内部的原始递归 的“编程语言”。因此模板可以用来“计算一个程序的结果”。

下面的代码在编译期间就能判断一个数是不是质数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<unsigned p, unsigned d> // p: number to check, d: current divisor 
struct DoIsPrime
{
static constexpr bool value=(p%d!=0)&&DoIsPrime<p,d-1>::value;
};
template<unsigned p> // end recursion if divisor is 2
struct DoIsPrime<p,2>
{
static constexpr bool value = (p%2 != 0);
};
template<unsigned p> // primary template
struct IsPrime
{ // start recursion with divisor from p/2:
static constexpr bool value = DoIsPrime<p,p/2>::value;
};
// special cases (to avoid endless recursion with template instantiation):
template<> struct IsPrime<0> { static constexpr bool value = false; };
template<> struct IsPrime<1> { static constexpr bool value = false; };
template<> struct IsPrime<2> { static constexpr bool value = true; };
template<> struct IsPrime<3> { static constexpr bool value = true; };

IsPrime<>模板将结果存储在其成员 value 中。为了计算出模板参数是不是质数,它实例化了 DoIsPrime<>模板,这个模板会被递归展开,以计算 p 除以 p/2 和 2 之间的数之后是否会有余数。

通过constexpr进行计算

C++11 引入了一个叫做 constexpr 的新特性,它大大简化了各种类型的编译期计算。如果给 定了合适的输入, constexpr 函数就可以在编译期间完成相应的计算。虽然 C++11 对 constexpr 函数的使用有诸多限制(比如 constexpt 函数的定义通常都只能包含一个 return 语句),但 是在 C++14 中这些限制中的大部分都被移除了。

在 C++14 中, constexpr 函数可以使用常规 C++代码中大部分的控制结构。因此为了判断一个 数是不是质数,可以不再使用笨拙的模板方式(C++11 之前)以及略显神秘的单行代码方式 (C++11),而直接使用一个简单的 for 循环:

1
2
3
4
5
6
7
8
constexpr bool isPrime (unsigned int p) 
{
for (unsigned int d=2; d<=p/2; ++d) {
if (p % d == 0) {
return false; // found divisor without remainder}
}
return p > 1; // no divisor without remainder found
}

但是上面所说的“可以”在编译期执行,并不是一定会在编译期执行。在需要编译期数值的上下文中(比如数组的长度和非类型模板参数),编译器会尝试在编译期对被调用的 constexpr 函数进行计算,此时如果无法在编译期进行计算,就会报错 (因为此处必须要产生一个常量)。在其他上下文中,编译期可能会也可能不会尝试进行编译期计算,如果在编译期尝试了,但是现有条件不满足编译期计算的要求,那么也不会报错, 相应的函数调用被推迟到运行期间执行

编译器if

通过使用 ifconstexpr(…)语法,编译器会使用编译期表达式来决定是使用 if 语句的 then 对应的部分还是 else 对应的部分

作为第一个例子,考虑 4.1.1 节介绍的变参函数模板 print()。它用递归的方法打印其参数(可能是任意类型)。如果使用 constexpif,就可以在函数内部决定是否要继续递归下去,而不用再单独定义一个函数来终结递归:

1
2
3
4
5
6
7
8
9
template<typename T, typename… Types>
void print (T const& firstArg, Types const&… args)
{
std::cout << firstArg << ’\n’;
if constexpr(sizeof…(args) > 0)
{
print(args…);//code only available if sizeof…(args)>0 (since C++17)
}
}