正如@celtschk's answer 所指出的,非平凡的析构函数正在阻止编译器将调用视为真正的尾递归。即使您的功能简化为:
template<typename T, T (*funct)(int) >
multiset<T> Foo(const multiset<T>& bar, int iterations) {
if (iterations == 0) return bar;
return Foo<T,funct>(bar, iterations - 1);
}
它仍然不是尾递归的,因为对递归函数调用结果的非平凡构造函数和析构函数的隐式调用。
但是请注意,上面的函数确实变成了尾递归,但变化相对较小:
template<typename T, T (*funct)(int) >
const multiset<T>& Foo(const multiset<T>& bar, int iterations) {
if (iterations == 0) return bar;
return Foo<T,funct>(bar, iterations - 1);
}
瞧!该函数现在编译成一个循环。我们如何使用您的原始代码实现这一目标?
不幸的是,这有点棘手,因为您的代码有时会返回副本,有时会返回原始参数。我们必须正确处理这两种情况。下面提出的解决方案是他的 cmets 对他的回答中提到的想法 David 的变体。
假设您想保持原来的 Foo 签名不变(而且,由于它有时会返回副本,因此没有理由相信您不想让签名保持不变),我们创建了一个辅助版本的Foo 称为Foo2,它返回对结果对象的引用,该对象是原始的bar 参数,或者是Foo 中的一个本地参数。此外,Foo 为副本创建占位符对象、辅助副本(用于切换)和一个保存funct() 调用结果的对象:
template<typename T, T (*funct)(int) >
multiset<T> Foo(const multiset<T>& bar, int iterations) {
multiset<T> copy_bar;
multiset<T> copy_alt;
T baz;
return Foo2<T, funct>(bar, iterations, copy_bar, copy_alt, baz);
}
由于Foo2 将始终返回一个引用,这消除了递归函数结果导致隐式构造和破坏的任何问题。
在每次迭代中,如果要将副本用作下一个bar,我们将副本传入,但切换副本的顺序和用于递归调用的替代占位符,以便替代实际用于在下一次迭代中保留副本。如果下一次迭代按原样重用bar,则参数的顺序不会改变,iterations 计数器只是递减。
template<typename T, T (*funct)(int) >
const multiset<T>& Foo2(
const multiset<T>& bar, int iterations,
multiset<T>& copy_bar, multiset<T>& copy_alt, T& baz) {
if (iterations == 0) return bar;
copy_bar = bar;
baz = funct(copy_bar.size());
if (baz.attr > 0) {
return Foo2<T, funct>(copy_bar, 100, copy_alt, copy_bar, baz);
} else {
return Foo2<T, funct>(bar, --iterations, copy_bar, copy_alt, baz);
}
}
请注意,只有对 Foo 的原始调用会支付构造和销毁本地对象的代价,而 Foo2 现在完全是尾递归的。