Tail recursion in C++

Can someone show me a simple tail-recursive function in C++?

Why is tail recursion better, if it even is?

What other kinds of recursion are there besides tail recursion?


A simple tail recursive function:

unsigned int f( unsigned int a ) {
   if ( a == 0 ) {
      return a;
   }
   return f( a - 1 );   // tail recursion
}

Tail recursion is basically when:

  • there is only a single recursive call
  • that call is the last statement in the function
  • And it's not "better", except in the sense that a good compiler can remove the recursion, transforming it into a loop. This may be faster and will certainly save on stack usage. The GCC compiler can do this optimisation.


    Tail recusion in C++ looks the same as C or any other language.

    void countdown( int count ) {
        if ( count ) return countdown( count - 1 );
    }
    

    Tail recursion (and tail calling in general) requires clearing the caller's stack frame before executing the tail call. To the programmer, tail recursion is similar to a loop, with return reduced to working like goto first_line; . The compiler needs to detect what you are doing, though, and if it doesn't, there will still be an additional stack frame. Most compilers support it, but writing a loop or goto is usually easier and less risky.

    Non-recursive tail calls can enable random branching (like goto to the first line of some other function), which is a more unique facility.

    Note that in C++, there cannot be any object with a nontrivial destructor in the scope of the return statement. The end-of-function cleanup would require the callee to return back to the caller, eliminating the tail call.

    Also note (in any language) that tail recursion requires the entire state of the algorithm to be passed through the function argument list at each step. (This is clear from the requirement that the function's stack frame be eliminated before the next call begins… you can't be saving any data in local variables.) Furthermore, no operation can be applied to the function's return value before it's tail-returned.

    int factorial( int n, int acc = 1 ) {
        if ( n == 0 ) return acc;
        else return factorial( n-1, acc * n );
    }
    

    Tail recursion is a special case of a tail call. A tail call is where the compiler can see that there are no operations that need to be done upon return from a called function -- essentially turning the called function's return into it's own. The compiler can often do a few stack fix-up operations and then jump (rather than call) to the address of the first instruction of the called function .

    One of the great things about this besides eliminating some return calls is that you also cut down on stack usage. On some platforms or in OS code the stack can be quite limited and on advanced machines like the x86 CPUs in our desktops decreasing the stack usage like this will improve data cache performance.

    Tail recursion is where the called function is the same as the calling function. This can be turned into loops, which is exactly the same as the jump in the tail call optimization mentioned above. Since this is the same function (callee and caller) there are fewer stack fixups that need to be done before the jump.

    The following shows a common way to do a recursive call which would be more difficult for a compiler to turn into a loop:

    int sum(int a[], unsigned len) {
         if (len==0) {
             return 0;
         }
         return a[0] + sum(a+1,len-1);
    }
    

    This is simple enough that many compilers could probably figure it out anyway, but as you can see there is an addition that needs to happen after the return from the called sum returns a number, so a simple tail call optimization is not possible.

    If you did:

    static int sum_helper(int acc, unsigned len, int a[]) {
         if (len == 0) {
            return acc;
         }
         return sum_helper(acc+a[0], len-1, a+1);
    }
    int sum(int a[], unsigned len) {
         return sum_helper(0, len, a);
    }
    

    You would be able to take advantage of the calls in both functions being tail calls. Here the sum function's main job is to move a value and clear a register or stack position. The sum_helper does all of the math.

    Since you mentioned C++ in your question I'll mention some special things about that. C++ hides some things from you which C does not. Of these destructors are the main thing that will get in the way of tail call optimization.

    int boo(yin * x, yang *y) {
        dharma z = x->foo() + y->bar();
        return z.baz();
    }
    

    In this example the call to baz is not really a tail call because z needs to be destructed after the return from baz. I believe that the rules of C++ may make the optimization more difficult even in cases where the variable is not needed for the duration of the call, such as:

    int boo(yin * x, yang *y) {
        dharma z = x->foo() + y->bar();
        int u = z.baz();
        return qwerty(u);
    }
    

    z may have to be destructed after the return from qwerty here.

    Another thing would be implicit type conversion, which can happen in C as well, but can more complicated and common in C++. For instance:

    static double sum_helper(double acc, unsigned len, double a[]) {
         if (len == 0) {
            return acc;
         }
         return sum_helper(acc+a[0], len-1, a+1);
    }
    int sum(double a[], unsigned len) {
         return sum_helper(0.0, len, a);
    }
    

    Here sum's call to sum_helper is not a tail call because sum_helper returns a double and sum will need to convert that into an int.

    In C++ it is quite common to return an object reference which may have all kinds of different interpretations, each of which could be a different type conversion, For instance:

    bool write_it(int it) {
          return cout << it;
    }
    

    Here there is a call made to cout.operator<< as the last statement. cout will return a reference to itself (which is why you can string lots of things together in a list separated by << ), which you then force to be evaluated as a bool, which ends up calling another of cout's methods, operator bool(). This cout.operator bool() could be called as a tail call in this case, but operator<< could not.

    EDIT:

    One thing that is worth mentioning is that a major reason that tail call optimization in C is possible is that the compiler knows that the called function will store it's return value in the same place as the calling function would have to ensure that its return value is stored in.

    链接地址: http://www.djcxy.com/p/14132.html

    上一篇: 结合记忆和尾巴

    下一篇: C ++中的尾递归