Topic: recursive function templates


Author: gjditchfield@watmsg.waterloo.edu (Glen Ditchfield)
Date: Wed, 11 Mar 1992 21:22:07 GMT
Raw View
This program is inspired by an example in Donahue & Demers' "Data Types are
Values", in TOPLAS v. 7 #3.
----------------------------------------
template<class T> void f(T t_p, long l) {
    struct s { T t_f } temp = {t_p};
    if (l > 0)
        f(temp, l - 1);
}
extern long random();
void main() {
    f(3.14, random());
}
----------------------------------------
   The template facility seems to have been designed to allow implemen-
tation by macro expansion, but that looks hopeless here.  When a template
function is generated from f for some type T1, another instance must
immediately be generated for the type "struct containing T1", and there is
no static way to put a useful upper bound on the number of generations
needed.  I don't see anything in the ARM that makes constructs like f
illegal.  What have I missed?

    Glen Ditchfield  gjditchfield@violet.uwaterloo.ca  Office: DC 2517
Dept. of Computer Science, U of Waterloo, Waterloo, Ontario, Canada, N2L 3G1
       Get a life -- before it gets you!




Author: jamshid@ut-emx.uucp (Jamshid Afshar)
Date: 16 Mar 92 10:40:09 GMT
Raw View
In article <1992Mar11.212207.20275@watmath.waterloo.edu>
gjditchfield@watmsg.waterloo.edu (Glen Ditchfield) writes:
>...
>template<class T> void f(T t_p, long l) {
>    struct s { T t_f } temp = {t_p};
>    if (l > 0)
>        f(temp, l - 1);
Cool!
>}
>extern long random();
>void main() {
>    f(3.14, random());
>}
>... and there is
>no static way to put a useful upper bound on the number of generations
>needed.

You're right, in your example there isn't a way to make it stop
instantiating template functions.  You can stop template expansion by
`specializing', but you can't specialize for local types.  Try
something like like the following instead:

 //---begin
 #include <iostream.h>

 template<class M> struct S {
    M m;
    S(const M& a_m) : m(a_m) {}
 };

 template<class T> void f(T t, long l) {
    S<T> tmp(t);   // BC++ erroneously(?) broke on `S<T> tmp = { t };'
    cout << &t << '\t' << l << '\t' << &tmp << endl;
    if (l>0)
       f(tmp, l-1);
 }

 // a specialization of `f()'
 void f(S< S< S< S< S<double> > > > > t, long l) {
    cout << &t << '\t' << l << '\t' << "STOP" << endl;
 }

 main() {
    double d = 3.141569;
    f(d, 5L);    // go from 5 to 0
 }
 //---end

>I don't see anything in the ARM that makes constructs like f illegal.

I also can't find anything explicitly making it illegal.  Another
situation that could make a C++ compiler go into an infinite loop is:

   class Test  {
   public:
      // or instead loop through another class defining `Test& operator->()'
      Test& operator->() { return *this; }
      int i;
   };
   main() { Test t; t->i = 0; return 0; }

I don't know if ANSI will, or even how exactly they would, specify
these kinds of things as having undefined results.  Would compilers be
required to diagnose them (like ANSI C requires for some constructs),
or could the compiler simply hang (ie, undefined compile-time
results)?  Is there anything analogous to this in ANSI C?

Btw, the appended has probably already been discovered by countless
others, but I think it's really neat and it helped me find a couple of
bizarre Borland C++ 3.0 bugs, so try it with your compilers.

Jamshid Afshar
jamshid@emx.utexas.edu

//---begin fact.cpp ---
// `Factorial<n>::compute()' is a cute way to compute at compile-time
// the factorial of a constant long `n'.

// Btw, compute() must be member func since template function parameters
// can only be types, not expressions.  Lippman's says removing this
// restriction is a likely ANSI C++ proposal, but the outcome is not clear.

#include <iostream.h>

template<unsigned long N> struct Factorial {
   static unsigned long compute() { return N*Factorial<N-1>::compute(); }
};

struct Factorial<0UL> {   // BC++ requires `L'
   static unsigned long compute() { return 1; }
};

// I think you should be able to just define the following instead
// of the entire class, but Borland C++ 3.0 run out of memory.
// inline unsigned long Factorial<0UL>::compute() { return 1; }

main() {
      // BC++ starts freaking out sometimes if J>10
   const unsigned long J = 10;   // must be compile-time constant, of course

   unsigned long f = 1, i=J;
   while (i>0) f*=i--;
   cout << "factorial(" << J << ") is " << f << endl;

   cout << J << "! is " << Factorial<J>::compute() << endl;
   return 0;
}

//---end fact.cpp ---