C++ 初探模板 (1)

120 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

编译期计算

我们可以利用C++的模板在编译期做一些有意思的事情

比如演算阶乘

template <int N> struct fact {
  static constexpr int value = fact<N - 1>::value * N;
};
template <> struct fact<0> { static constexpr int value = 1; };

然后我们就可以 fact<10>::value这样来得到一个常量值

注意,上述所展示的模板计算全是在编译期进行的,所以我们就可以O(1)计算阶乘了?

理论上是这样,只要你保证不溢出就ok

这个例子也是模板编程解决一类问题的典型示例

又比如另一个模板技巧

template <typename V, int N> size_t len(const V (&)[N]) { return N; }

上述可以计算一个编译期确定的数组所拥有的长度

编译期循环

现在,想一想,要实现编译期的循环如何实现?

利用特化手段?

好像可行耶

来搞一搞

template <int begin, int end> struct for_each {
  static void run() {
    std::cout << begin << " \n";
    for_each<begin + 1, end>::run();
  }
};
template <int begin> struct for_each<begin, begin> {
  static void run() { std::cout << begin << " \n"; }
};

好,轻松实现一个 for_each<1, 10>::run(); 打印 1 - 10的值,虽然是一个递归

但是,对于模板编程中的有一些操作来说,够用了,更多的操作为什么不留到运行期做呢?

上面的操作写得麻烦啊,有没有什么解决的办法

下面介绍两个可以简化的方法

if constexpr

利用C++ 17的 常量期 if

template <int begin, int end> void for_each() {
  std::cout << begin << " ";
  if constexpr (begin < end)
    for_each<begin + 1, end>();
}

看得出来,常量器if还是要简化代码不少的

利用 index_sequence

index_sequence 是 C++14出的一个新玩意 我们可以用它来生成一串序列参数

template <int> void for_each_help() {}
template <int begin, typename T, typename... V>
void for_each_help(T t, V... v) {
  std::cout << begin + t << " ";
  for_each_help<begin>(v...);
}
template <int begin, std::size_t... Is>
void for_each_(std::index_sequence<Is...>) {
  for_each_help<begin>(Is...);
}
template <int begin, int end> void for_each() {
  for_each_<begin>(std::make_index_sequence<end - begin>());
}

index_sequence + flow expression

上面的代码太复杂了,我们来简化简化

C++ 17 出了个 flow expression

我们可以利用一下

template <int begin, std::size_t... Is>
void for_each_(std::index_sequence<Is...>) {
  ((std::cout << begin + Is << " "), ...);
}
template <int begin, int end> void for_each() {
  for_each_<begin>(std::make_index_sequence<end - begin>());
}

来写一道题 ?

光说不练假把式,我们来看一道在线OJ上的题目

leetcode.cn/problems/qi…

青蛙跳台阶问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1

数据范围:

0 <= n <= 100

解法

一般的解法太无趣了,我们来一个编译期的解法(0 开销?)

case 1:
class Solution {
public:
  static constexpr int MOD = 1e9 + 7;
  unordered_map<int, int> mp;
  template <int N> struct fun {
    //  enum { value = (fun<N - 1>::value + fun<N - 2>::value) % MOD };
    static constexpr int value = (fun<N - 1>::value + fun<N - 2>::value) % MOD;
  };
  template <> struct fun<1> {
    //  enum { value = 1 };
    static constexpr int value = 1;
  };
  template <> struct fun<0> {
    //  enum { value = 1 };
    static constexpr int value = 1;
  };
  template <int NOW, int END> void for_each() {
    mp.insert({NOW, fun<NOW>::value});
    if constexpr (NOW != END) {
      for_each<NOW + 1, END>();
    }
  }
  Solution() { for_each<0, 100>(); }
  int numWays(int n) { return mp[n]; }
};
case 2
class Solution {
public:
  static const int MOD = 1e9 + 7;
  unordered_map<int, int> mp;
  template <int N> struct fun {
    static constexpr int value = (fun<N - 1>::value + fun<N - 2>::value) % MOD;
  };
  template <> struct fun<1> { static constexpr int value = 1; };
  template <> struct fun<0> { static constexpr int value = 1; };
  template <std::size_t begin, std::size_t... Is>
  void for_each_help(std::index_sequence<Is...>) {
    ((mp.insert({begin + Is, fun<Is>::value})), ...);
  }
  template <int begin, int end> void for_each() {
    for_each_help<begin>(std::make_index_sequence<end - begin>{});
  }
  Solution() { for_each<0, 101>(); }
  int numWays(int n) { return mp[n]; }
};

上述两种写法都是编译期计算的dp值
有点类似于记忆化搜索的样子

很好的展示了模板循环展开和模板编译期计算的功能