【问题标题】:What is induction method when it comes to C++ template metaprogramming?什么是 C++ 模板元编程的归纳法?
【发布时间】:2015-12-18 16:05:22
【问题描述】:

当谈到模板元程序时,人们一直在说使用归纳法解决问题。例如看到这个答案:https://stackoverflow.com/a/11811486/4882052

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

【问题讨论】:

  • 你了解“递归”吗? “归纳”只是从不同的角度看待递归。在每种情况下,您都需要一个或多个基本案例,其中一个问题可以在没有递归的情况下解决,并且您需要一个递归案例,其中可以通过使用更接近基本案例的相关问题的解决方案来解决问题。跨度>
  • 在运行时递归编程中,基本情况可以通过运行时条件来检测。在递归元编程中,即使是编译时条件也不足以处理基本情况。您需要使用重载或专门化的单独定义来涵盖基本情况。
  • @JSF:为什么这不是答案?
  • @NicolBolas 因为问题的含义不够清楚,并且因为问题要求示例,而我提供了概念。主要是我不知道 Angelus Mortis 对 我的 问题“你了解递归吗?
  • @JSF 开始写答案,我是递归大师相信我:p

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


【解决方案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 代码中的循环展开之前完成的类型的优化是'直到循环展开后才可见。

【讨论】:

  • 喜欢你的例子 :) 谢谢
【解决方案2】:

正如在 OP 下的一个 cmets 中所观察到的,TMP 技术本质上是递归的,我猜这可以看作是“反向归纳”的一种形式(最初是由 Fermat 提出的一个想法)。这个想法是,对于某个 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) 由模板给出,其值根据(可能更专业的)模板的较小值定义,终止于某个下限。

【讨论】:

    【解决方案3】:

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

    • 证明 X 对于某个值 Y(通常是微不足道的)为真
    • 表明如果 X 对 Y 成立,那么对于其他值 Y + delta 仍然成立
    • 因此得出结论,对于所有 Y + delta * N,X 都为真

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

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

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

    void in_order(Tree *t) { 
        if (nullptr == t)
            return;
        in_order(t->left);
        process(t);
        in_order(t->right);
    }
    
    // preorder and postorder are same except for the order of `process` vs. recursion.
    

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

    【讨论】:

    • 非常好的解释先生 :) 谢谢 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-04
    • 2015-12-03
    相关资源
    最近更新 更多