As indicated by @celtschk's answer, non-trivial destructors are preventing the compiler from treating the calls as truly tail-recursive. Even when your function is reduced to this:
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);
}
It is still not tail-recursive, because of the implicit calls to the non-trivial constructors and destructors of the result of the recursive function call.
Note, however, that the above function does become tail-recursive with a relatively minor change:
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);
}
Voila! The function now compiles down into a loop. How can we achieve this with your original code?
Unfortunately, it is a little tricky, because your code will sometimes return the copy, and sometimes the original argument. We have to correctly deal with both cases. The solution presented below is a variation of the idea David mentioned in his comments to his answer.
Assuming you want to keep your original Foo
signature the same (and, since it sometimes returns the copy, there is no reason to believe you wouldn't want to leave the signature the same), we create a secondary version of Foo
called Foo2
that returns a reference to a result object that is either the original bar
parameter, or one local in your Foo
. In addition, Foo
creates place holder objects for the copy, a secondary copy (for switching), and one to hold the funct()
call result:
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);
}
Since Foo2
will always end up returning a reference, this removes any issues with the recursive function result causing implicit construction and destruction.
Upon each iteration, if the copy is to be used as the next bar
, we pass the copy in, but switch the order of the copy and alternate placeholders for the recursive call, so that the alternate is actually used to hold the copy on the next iteration. If the next iteration reuses bar
as is, the order of the arguments don't change, the iterations
counter is merely decremented.
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);
}
}
Notice that only the original call to Foo
pays the penalty of construction and destruction of the local objects, and Foo2
is now entirely tail-recursive.