问题
I was reading the responses to "Printing 1 to 1000 without loop or conditionals" and I am wondering why it is necessary to have the special case for NumberGeneration<1> in the top answer.
If I remove that and add a check for N == 1 in the template (code below), the code fails compilation with "template instantiation depth exceeds maximum" but I'm not sure why. Are conditionals handled differently in compile-time?
#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);
}
回答1:
Code generation and compilation doesn't branch depending on conditionals! Consider this:
// don't declare bar()!
void foo()
{
if (false) { bar(); }
}
If you never declare bar()
, this is a compilation error even though the inner scope can never be reached. For the same reason, NumberGeneration<N-1>
is always instantiated, no matter whether that branch can be reached or not, and you have infinite recursion.
Indeed, the static analogue of conditionals is precisely template specialization:
template <> struct NumberGeneration<0> { /* no more recursion here */ };
回答2:
The conditional if
will not be handled at compile time. It will be handled at runtime.
Thus, even for N=1, the compiler will generate NumberGenerator<0>, then NumberGenerator<-1> ... endlessly, until reaching template instantiation depth.
回答3:
Templates are instantiated at compile time, the template special case prevents the compiler from recursing below 1 when compiling.
if-clauses are evaluated at runtime, so the compiler has already failed at compiling your code when it would have any effect.
回答4:
I am wondering why it is necessary to have the special case for NumberGeneration<1> in the top answer.
Because that's the end condition for recursive ! Without that how could the recursive end ?
回答5:
In general the condition N == 1
in your code is evaluated at run time (although compiler may optimize this away), not at compile time. Therefore template instantiation recursion in the else
clause is never terminated. NumberGeneration<1>
on the other hand is evaluated at compile time and therefore acts as a termination case of this recursive template.
回答6:
I'm fairly sure this is compiler-specific; some compilers may try to generate both branches of the if
/else
whatever the value of N
, in which case compilation will fail in any event. Other compilers may evaluate the condition at compile time, and only generate code for the branch that's executed, in which case compilation will succeed.
UPDATE: Or as Luc says in the comments, it may be that the compiler must generate both branches, so that the code will always fail. I'm not quite sure which is the case, but either way it's a bad idea to rely on run-time conditions to control compile-time code generation.
It would be better to use specialisation:
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;
}
(or you could shorten this slightly by instead specialising for N=0
, with an out
function that does nothing).
Also, be aware that some compilers may not support deeply resursive templates; C++03 suggests a minimum supported depth of only 17, which C++11 increases to 1024. You should check what your compiler's limit is.
回答7:
It's because integers can be negative and runtime code (the if
check) won't stop the compiler instantiating the template with 0, -1, -2, etc. A compiler might be able to get away with what you propose, but what if instantiating the other templates (0, -1, ...) has side effects that you depend on? The compiler can't fail to instantiate them for you in that case.
In short, just like all recursion you have to provide your own base case.
回答8:
Here's the correct way to do it:
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);
}
That's called template specialization: you, the programmer, provide an alternative definition for a particular instantiation of a template. You can specialize the entire template, or only a portion of it, as I did here (I only specialized the function, not the entire struct).
来源:https://stackoverflow.com/questions/8759872/compile-time-recursion-and-conditionals