人们一直在说模板元程序时使用归纳来解决问题。例如,请参阅此答案:https://stackoverflow.com/a/11811486/4882052
我知道感应证明等,但这个理论如何用来解决元程序?我喜欢带有例子的例子:)
答案 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)
归纳证明通常具有以下结构:
(......在很多情况下,如果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
中展开在循环展开完成后才能看到代码。