编译时间递归和条件

时间:2012-01-06 15:07:17

标签: c++ compile-time

我正在阅读对"Printing 1 to 1000 without loop or conditionals"的回复,我想知道为什么有必要为NumberGeneration< 1>设置特殊情况。在最佳答案中。

如果我删除它并在模板中添加N == 1的检查(下面的代码),代码将无法编译“模板实例化深度超过最大值”,但我不确定原因。条件在编译时是否处理不同?

#include <iostream>

template<int N>
struct NumberGeneration
{
    static void out(std::ostream& os)
    {
        if (N == 1)
        {
            os << 1 << std::endl;
        }
        else
        {
            NumberGeneration<N-1>::out(os);
            os << N << std::endl;
        }
    }
};

int main()
{
    NumberGeneration<1000>::out(std::cout);
}

8 个答案:

答案 0 :(得分:12)

代码生成和编译根据条件不分支!考虑一下:

// don't declare bar()!

void foo()
{
     if (false) { bar(); }
}

如果您从未声明bar(),那么这是编译错误,即使永远无法达到内部范围。出于同样的原因,NumberGeneration<N-1>总是被实例化,无论是否可以到达那个分支,并且你有无限的递归。

实际上,条件的静态模拟恰好是模板特化:

template <> struct NumberGeneration<0> { /* no more recursion here */ };

答案 1 :(得分:4)

条件if将不会在编译时处理。它将在运行时处理。

因此,即使对于N = 1,编译器也将生成NumberGenerator&lt; 0&gt;,然后生成NumberGenerator&lt; -1&gt;。 ...无休止地,直到达到模板实例化深度。

答案 2 :(得分:3)

模板在编译时被实例化,模板特殊情况会阻止编译器在编译时在1以下递归。

在运行时评估if子句,因此编译器在编译代码时已经失败了。

答案 3 :(得分:1)

  

我想知道为什么有特殊情况需要   NumberGeneration&LT 1为卤素;在最佳答案中。

因为这是递归的最终条件!没有它,递归结束怎么样?

答案 4 :(得分:1)

通常,代码中的条件N == 1在运行时进行评估(尽管编译器可能会优化它),而不是在编译时。因此,else子句中的模板实例化递归永远不会终止。另一方面,NumberGeneration<1>在编译时进行评估,因此充当此递归模板的终止案例。

答案 5 :(得分:1)

我很确定这是特定于编译器的;一些编译器可能会尝试生成if / else的两个分支,无论N的值是什么,在这种情况下,编译将无论如何都会失败。其他编译器可以在编译时评估条件,并仅为执行的分支生成代码,在这种情况下编译将成功。

更新:或者正如Luc在评论中所说,可能是编译器必须生成两个分支,因此代码总是会失败。我不太确定是哪种情况,但无论哪种方式依靠运行时条件来控制编译时代码生成都是一个坏主意。

最好使用专业化:

template <int N>
struct NumberGeneration
{
    static void out(std::ostream & os)
    {
        NumberGeneration<N-1>::out(os);
        os << N << std::endl;
    }
};

template <>
void NumberGeneration<1>::out(std::ostream & os)
{
    os << 1 << std::endl;
}

(或者您可以通过专门针对N=0而使用out无效的函数来缩短这一点。

另外,请注意某些编译器可能不支持深度复原模板; C ++ 03建议最小支持深度仅为17,C ++ 11增加到1024.您应该检查编译器的限制是什么。

答案 6 :(得分:0)

这是因为整数可以是负数,运行时代码(if检查)不会停止编译器使用0,-1,-2等实例化模板。编译器可能能够逃避你的建议,但如果实例化其他模板(0,-1,...)会产生你依赖的副作用呢?在这种情况下,编译器无法为您实例化它们。

简而言之,就像所有递归一样,你必须提供自己的基本案例。

答案 7 :(得分:0)

这是正确的方法:

template<int N>
struct NumberGeneration
{
    static void out(std::ostream& os);
};

template<int N>
void NumberGeneration<N>::out(std::ostream& os)
{
    NumberGeneration<N-1>::out(os);
    os << N << std::endl;
}

template<>
void NumberGeneration<1>::out(std::ostream& os)
{
    os << 1 << std::endl;
}

int main()
{
    NumberGeneration<20>::out(std::cout);
}

这称为模板专业化:程序员,您为模板的特定实例化提供了另一种定义。你可以像我在这里那样专门化整个模板,或者只是它的一部分(我只专注于函数,而不是整个结构)。