什么是C ++模板元编程的归纳法?

时间:2015-12-18 16:05:22

标签: c++ templates template-meta-programming

人们一直在说模板元程序时使用归纳来解决问题。例如,请参阅此答案:https://stackoverflow.com/a/11811486/4882052

我知道感应证明等,但这个理论如何用来解决元程序?我喜欢带有例子的例子:)

3 个答案:

答案 0 :(得分:1)

正如在OP中的一条评论中所观察到的那样,TMP技术本质上是递归的,我猜这可以看作是一种“反向归纳”(一种最初归因于费马的想法)。这个想法是,对于某些N,你可以用一些较小的N来定义你想要的相应的东西,最终在一些基本情况下终止。

考虑以下用于阶乘的TMP代码:

template <int N>
struct Factorial {
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> {
    enum { value = 1 };
};

void foo() {
    std::cout << Factorial<0>::value << "," << Factorial<3>::value; 
    // outputs 1, 6
}

因此,一般情况(N)由一个模板给出,该模板的值根据(可能更专业的)模板的较小值定义,终止于某个下限。

答案 1 :(得分:1)

归纳证明通常具有以下结构:

  • 表明对于某些值Y
  • ,X(通常是平凡的)为真
  • 显示如果Y对于Y为真,那么对于其他值Y + delta
  • ,它仍然为真
  • 因此得出结论,X对于所有Y + delta * N
  • 都是正确的

(......在很多情况下,如果delta为1,它真的很方便,所以我们可以说“X对所有非负整数都是正确的”,或者那个顺序上的东西)。在相当多的情况下,将证明扩展到两个方向也很方便,因此我们可以说X对于所有整数都是正确的(对于一个明显的例子)。

大多数纯粹的递归解决方案(无论是模板元编程还是其他方式)都倾向于遵循大致相同的结构。特别是,我们从处理一些简单的案例开始,然后根据基本案例的应用加上一些扩展步骤来定义更复杂的案例。

暂时忽略模板元编程,这可能是在树的预订,顺序和后序遍历的递归算法中最容易看到的。对于这些,我们定义了一个基本案例,用于处理树的单个节点中的数据。这通常与树遍历本身无关,因此我们通常只将其视为名为NavigationController的函数或类似的函数。有了这个,我们可以定义树遍历,如:

process

许多人认为这是独特的(或者至少是非常适用于)模板元编程的原因是它是一个C ++真正只允许纯粹递归解决方案的领域 - 与普通的C ++不同,你没有循环或变量。很长一段时间以来,还有其他类似的语言,但大多数都没有真正达到主流。有很多语言倾向于遵循这种风格,即使它们并不真正需要它 - 但是当它们中的一些已经接近主流时,它们中的大多数仍然处于边缘。

答案 2 :(得分:1)

“归纳”只是从不同的角度看待递归。在每个中你需要一个或多个基本情况,在这种情况下,问题可以在没有递归的情况下解决,你需要一个递归的情况,通过使用更接近基本情况的相关问题的解决方案可以解决问题。

在运行时递归编程中,可以通过运行时条件检测基本情况。在递归元编程中,即使编译时条件也不足以处理基本情况。您需要使用重载或专门的单独定义来涵盖基本情况。

我自己第一次使用它是一个相当混乱的情况,我不能完全引用,但总体思路可能是有益的。编译器在展开短循环之前进行了各种优化,并在展开短循环后进行了各种其他优化。但我真的需要在之前做过的“之前”优化之一。所以我需要强制编译器在编译之前解开一些短循环,粗略地说:

template<unsigned N>
struct unwind {
   void operator()(X*p) { unwind<N-1>()(p); work(p[N]); } };
template<>
struct unwind<0> {
   void operator()(X*p) { work(p[0]); } };

当您使用编译时递归而不是运行时循环时,编译器将在执行任何优化之前解开整个循环,因此在循环展开之前完成的类型的优化在我work中展开在循环展开完成后才能看到代码。