第八章:模板元编程

2021-02-22  本文已影响0人  找不到工作

模板是在编译时实例化的。C++ 的某些功能可以与实例化过程结合,成为一种原始的递归“编程语言”。C++ 有多种功能来支持编译器编程。

8.1 模板元编程

我们以一个判断一个数是否是质数的函数为例子讲解模板元编程。以下给出了最原始的基于 c98 版本的实现。

基本思路是通过模板特化生成很多个类,每个类的参数是数字以及被除数,并提供一个值 value 来表示是否是质数。

// p: number to test if it is a prime, d: divisor
template<unsigned p, unsigned d>
struct DoIsPrime {
  static constexpr bool value = (p%d != 0) && DoIsPrime<p, d-1>::value;
};

// specialize for d = 2
template<unsigned p>
struct DoIsPrime<p,2> {
  static constexpr bool value = (p%2 != 0);
};


// start recursive process from n/2
// ideally sqrt(n), but not supported in compile time
template<unsigned p>
struct IsPrime {
  static constexpr bool value = DoIsPrime<p, p/2>::value;
};

// end condition
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;
};

核心代码是递归调用(生成类型DoIsPrime<p, d-1>),直到终止条件 d = 2。中途发现任何可以整除的数字,即返回不是质数。

// p: number to test if it is a prime, d: divisor
template<unsigned p, unsigned d>
struct DoIsPrime {
  static constexpr bool value = (p%d != 0) && DoIsPrime<p, d-1>::value;
};

这个语法非常晦涩,而且要求使用 struct 而不能使用函数实现。但是揭示了模板元编程的本质。

8.2 使用 constexpr 简化

c++11 引入了 constexpr,可以在编译器计算表达式(甚至函数)的值。但是限制是:无法使用堆、无法抛出异常、仅支持一行表达式。

利用 constexpr,我们可以把上面冗长的代码简化为简单的两个函数:

// c11 introduced constexpr, but supports only 1 statement
// this is the reason of using ternary operator
constexpr bool
doIsPrime(unsigned p, unsigned d)
{
  return d==2 ? (p%d != 0) :
                (p%d != 0) && doIsPrime(p, d-1);
}

constexpr bool
isPrime(unsigned p)
{
  return (p<3) ? p == 2 : doIsPrime(p, p/2);
}

值得注意的是,c11 中 constexpr 只支持一行语句。因此,我们的函数都利用三元操作符? A:B 强行缩减成了一行。

自 c14 开始,constexpr修饰的函数可以使用大部分的控制语句 ifforswitch。因此可以改成更加自然的形式:

// "if" is supported from c14
constexpr bool
doIsPrime(unsigned p, unsigned d)
{
  if (d == 2) {
    return p % 2 == 0;
  }
  return (p%d != 0) && doIsPrime(p, d-1);
}

constexpr bool
isPrime(unsigned p)
{
  if (p < 2) {
    return false;
  }

  if (p == 2 || p == 3) {
    return true;
  }
  
  return doIsPrime(p, p / 2);
}

8.4 SFINAE

C++ 中函数的重载(overload)非常普遍。当编译器处理一个重载函数调用时,它需要分别考虑所有备选项并选择最匹配的一个。

编译器首先需要决定用什么模板参数,然后替换这些参数,并比较匹配程度。但是,替换的过程可能会产生错误的或者无意义的结果。编译器会忽略这种错误。

这就被称为 SFINAE (Substitution Failure Is Not An Error) 原则。

上一篇 下一篇

猜你喜欢

热点阅读