From 2462037992650712009
X-Google-Language: ENGLISH,ASCII-7-bit
X-Google-Thread: f78e5,9d57bb38940f08c2
X-Google-Attributes: gidf78e5,public
From: sbnaran@localhost.localdomain.COM (Siemel Naran)
Subject: Re: References to functions allowed?
Date: 1999/01/25
Message-ID: <slrn7anvrd.8jt.sbnaran@localhost.localdomain>
X-Deja-AN: 436749496
X-NNTP-Posting-Host: arboria-56.slip.uiuc.edu
Approved: stephen.clamage@sun.com (comp.std.c++)
References: <36A222B3.E51DFD28@ibm.net> <780u86$vj4$1@nnrp1.dejanews.com>
X-UID: 0000000001
X-Status: $$$T
Organization: University of Illinois at Urbana-Champaign
Reply-To: sbnaran@uiuc.edu
Newsgroups: comp.std.c++
Originator: clamage@taumet


On 19 Jan 1999 15:51:29 GMT, AllanW@my-dejanews.com

An amusing reply, but in previous times, I had seriously considered why
C++ doesn't have self-modifying functions.  The existence of
self-modifying functions seem to imply the existence of nested or lambda
functions.

>Consider that C++ can do almost anything that assembly language can do.
>But there is one powerful programming technique that has fallen out of
>favor recently, a mood swing which has so blinded the C++ standardization
>committee that it wasn't even considered for inclusion in the language.
>Which is bad, because the technique I am referring to is time-honored,
>well-understood, and very very powerful. Of course I cannot be talking
>about anything other than self-modifying code.

There is much time spent modifying the functions, which is probably one
reason why they didn't include it in the language.  A second reason is
that if functions were modifiable, then a function couldn't have a
fixed address in memory.  Instead, we would have a pointer/reference to
the actual function.  This incurs a small space and time overhead.  The
space overhead is because we need a pointer to the function along with
the function.  The time overhead is because we need to dereference the
pointer each time we want to call the function.  Also, forget about
inlining.

>    #include <iostream>
>    const int MAXVAL = 5;
>    int nextval(int) { return 0; }
>    int decrement(int);
>    int increment(int x) {
>        ++x;                // Must update BEFORE overwrite
>        if (x>=MAXVAL) {
>            int (&me)(int) = nextval;
>            int (&const other)(int) = decrement;
>            me = other; // Overwrite nextval() with copy of decrement
>        }
>        return x;
>    }
>    int decrement(int x) {
>        --x;                // Must update BEFORE overwrite
>        if (x<=0) {
>            int (&me)(int) = nextval;
>            int (&const other)(int) = increment;
>            me = other; // Overwrite nextval() with copy of increment
>        }
>        return x;
>    }
>
>    int main() {
>        // Also inits nextval() with copy of increment:
>        int v = decrement(1);
>        std::cout << v;
>        for (int i=1; i<=20; ++i) std::cout << ',' << (v=nextval(v));
>        std::cout << std::endl;
>    }
>    // Output: 0,1,2,3,4,5,4,3,2,1,0,1,2,3,4,5,4,3,2,1,0


BTW, why not just say "nextval=increment" or "nextval=decrement"?

Besides, I don't think the technique will be much used.  For in general,
the functions 'increment' and 'decrement' would have to contain static
variables to represent their state.  This way, they know when to mutate
the function 'nextval'.  For example, each could have a counter that
goes from one to five.  When the counter hits five, we reset it to zero
and mutate the function 'nextval'.  In your case, the variable 'x'
passed to the function serves as the state variable.  But this seems
like a special case, for in general, state variables would be required.

The problem with static variables is that there is only one copy of
them.  This rules out multi-threading.  Single-threading is a problem
too because we might want to interleave two sequences.  This leaves us
with one possibility -- make 'nextval' an object of type NextVal with
the state variables as private data members.

What we've described is the wrapper technique to get the effect of
mutating functions.  The class NextVal is a wrapper that contains
within it a pointer to a function (or a polymorphic class, or
whatever), along with a couple of state variables.  The class NextVal
behaves either as the function 'increment' or the function
'decrement'.  It changes its behaviour according to input from the
outside world.  The end result is the effect of mutating functions.

--------------------------------------------------------------

#include <cstddef> // size_t

//
// pre-existing functions
//
int increment(int x) { return x+1; }
int decrement(int x) { return x-1; }


//
// class to coordinate the above two functions
//

class NextVal
{
   class Function
   {
      private:
         size_t indx;
         int (*function)(int);
         static int (*const functions[2])(int);
      public:
         Function() : indx(0), function(functions[indx]) { }
         Function& operator++()
         {
            const size_t N=sizeof(functions)/sizeof(*functions);
            function=functions[(function==functions[N-1])?(indx=0):++indx];
            return *this;
         }
         int operator()(int x) { return (*function)(x); }
   };

   private:
      int counter;
      Function function;
      
   public:
      NextVal() : counter(), function() { }
      int operator()(int num)
      {
         int newnum=function(num);
         if (++counter==5) { ++function; counter=0; }
         return newnum;
      }
};

int (*const NextVal::Function::functions[2])(int)={&::increment,&::decrement};


//
// test driver
//

#include <iostream>

int main()
{
   NextVal v;
   int j;
   cout << (j=v(0));
   for (int i=0; i<20; i++) cout << ',' << (j=v(j));
}
--------------------------------------------------------------


>As you can see, the first thing main() does is to call decrement(). That
>function causes the code in function increment() to overwrite function
>nextval(). From then on we call nextval() only. When we have incremented
>as much as we want to, nextval() overwrites itself with a copy of
>decrement(), which takes effect next time the function is called.

There is time spent overwriting the function.


>I hear some of you object right now. "Couldn't you accomplish the same
>thing by having two classes with virtual functions, and then overwriting
>the vtables with the new function addresses?" Perhaps, but this gets
>tricky. A simpler technique would be to modify the vptr itself, to point
>to the other vtable. But even this crystal-clear idea has drawbacks; it
>just isn't a general-purpose solution to the problem. To see why, imagine
>that main() is a legacy function that you cannot make changes to. Since
>main() calls nextval() directly, the only general-purpose solution to
>the problem is to overwrite the code in nextval() at run time.

Yes, any of the C++ techniques would work.  If we're coding in C++ but
maintaining legacy code written in C, then we can use the wrapper
technique.  Just write a class like the one above.  (The 'indx' field
is essentially a virtual pointer, so the class NextVal above is
essentially polymorphic.)

If we're coding in C and maintaining legacy code in C, then the wrapper
technique is still possible because it is possible to have classes in
C too.  Like this:

   // NextVal.h
   struct NextVal;
   NextVal * NextVal_construct();
   void NextVal_destruct();
   int NextVal_do(int);
   
   // NextVal.c
   #include "NextVal.h"
   struct NextVal { ... }
   ...



>An even worse idea is to create a fancy version of nextval() with large
>if-then sections, or to use some method that checks flags (or anything
>else) and "dispatches" to some other function to do the actual work. As
>all assembly-language programmers know, irresponsible solutions like
>this cause extra memory fetches. For instance, a Cray computer might
>take as much as 20 nanoseconds longer if the above program were written
>in this way. If run 1000 times a day for 30 years, this would add up to
>well over 0.2 seconds -- that's 0.2 very expensive seconds of Cray CPU
>time, and also 0.2 seconds of your personal time that you could have
>spent with your loved ones, or simply relaxing.

:)

-- 
----------------------------------
Siemel B. Naran (sbnaran@uiuc.edu)
----------------------------------


[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://reality.sgi.com/austern_mti/std-c++/faq.html              ]




