Bookmark and Share

Notice: On March 31, it was announced that Statalist is moving from an email list to a forum. The old list will shut down at the end of May, and its replacement, statalist.org is already up and running.


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: st: Pointer to class member function?


From   "William Gould, StataCorp LP" <wgould@stata.com>
To   statalist@hsphsun2.harvard.edu
Subject   Re: st: Pointer to class member function?
Date   Fri, 03 May 2013 11:36:56 -0500

Richard Foltyn (richard.foltyn@gmail.com) is doing advanced class
programming in Mata.  He is trying to take the address of a
class-member function and then use that address later to invoke the
function.

The short answer is that what Richard is trying to do will not work and 
is not supported.  

I'm about to explain why it's not supported, but I don't really expect
that will alleviate Richard's disappointment.

Richard offered the following example:

        mata:

        class testClass {
                void run1()
                void run2()
                real scalar func()
        }

        real scalar testClass::func(real scalar arg) 
        {
            return(arg)
        }

        void testClass::run2(pointer(real scalar function) scalar f, ///
            real scalar arg) 
        {
            (*f)(arg)
        }

        void testClass::run1() 
        {
            this.run2(&func(), 1)
        }

        foo = testClass()
        foo.run1()

        end

The result of running example, Richard correctly reports, is, 

        : foo.run1()
               testClass::run1():  3499  func() not found [3]
                         <istmt>:     -  function returned error [1]

Richard took a good stab and writing what wanted to achieve.  Even so,
there is conceptually too little information in what Richard wrote
above for any compiler to interpret or to compile the code correctly.

To understand the issue, I first need to tell you how class functions
are implemented by compilers.  When a programmer codes

        a.myfunc()

The compiler in fact compiles 

        <name_of_class>memberfunction_myfunc(a)

That is, the a. at the front becomes an argument to more-or-less regular 
function!  Details vary, but all compilers do this.  When programmer
codes

        a.func2(d, e)

compiles in fact compile 

        <name_of_class>memberfunction_func2(a, d, e)

The class object again moves to being just an argument of the function.

The class object had to to be passed to the function in some way, and
because the mechanism already exists to pass arguments, it's easiest
for compilers to simply pass the class object using them.

So now consider one of the functions Richard wrote, 


        void testClass::run1() 
        {
            this.run2(&func(), 1)
        }

That is where the error occurred, but at this point, the conceptual
problem has not yet arisen.  It is merely that Mata will not allow the
& operator to be applied to member functions.  That is not implemented
because, as I am about to show you, even if you had the address, we are
about to run into a substantive problem.

So pretend that Mata did return the address of member function
func().  It would have been easy enough for Mata do to that.  In fact,
extra code had to be added to prevent Mata from doing that.

That substantive problem would arise when Richard when to use the
pointer.  In Richard's case, that would be in the following code:

        void testClass::run2(pointer(real scalar function) scalar f, ///
            real scalar arg) 
        {
            (*f)(arg)
        }

All Mata knows about object f is that it is a pointer to a real scalar
function.  It does not know that the pointer is in fact a pointer to a
member function of some class, nor does it know the name of the class.
Thus, if Mata were to compile

        (*f)(arg) 

It would not know that it needed to add a first argument to the
function containing a pointer to the class object!  So even if we had
the address of the member function, the code would be miscompiled and
that would lead to another error.

And that is why Mata won't let you take the address of a member
function.  There is nothing useful you could do with it.

As I said, we actually went to extra work to prevent Mata from
returning the address of the function.  So how might we extend the
programming language so that in the definition of member function
run2() Mata would know that *f is a member of function and of this same
class.

One way would be, 

        void testClass::run2(
                pointer(real scalar member(testClass) function) scalar f,
                real scalar arg)
        {
                (*f)(arg)
        }

We could teach Mata to understand that, although we will not do that.

One feature of Mata is that Mata never crashes.  Errors are
caught and Stata stays running.  If we opened this door, You
could specify the name of the class incorrectly.  How would we
know that you did not?  If you did, a crash would be possible
unless we also add run-time code to catch the problem, and that will 
slow down the execution of all functions in all contexts. 

In C++, you can do something like Richard wants, but if you do it
incorrectly, the resulting code crashes.  Moreover, even in C++, the
standard implementations do not provide exactly what Richard wants.
C++ requires that a pointer to the class object be passed along with a
pointer to the function.  And you must be sure that you get the
pointers right or the resulting code crashes.

One solution Richard might consider is to move the functions outside
of the class and pass the class object explicitly to the functions.
Mata does allow pointers to ordinary functions and they may be used
even inside classes.

The other possible solution, which can be implemented inside the class, 
is to use codes rather than address and a switcher function inside 
the class to call the appropriate function.  The code would look like this:


        mata:

        class testClass {
                void run1()
                void run2()
                real scalar func(), func2(), func3()
                real scalar switch()
        }

        real scalar testClass::func(real scalar arg) 
        {
            return(arg)
        }

        /* define func2(), func3() how you wish */

        real scalar  testClass::switch(real scalar id, real scalar arg)
        {
                if (id==1) return(this.func(arg))
                if (id==2) return(this.func2(arg))
                if (id==3) return(this.func3(arg))
                _error("invalid id"
        }

        ...

-- Bill                   -- Hua Peng
   wgould@stata.com          hpeng@stata.com

*
*   For searches and help try:
*   http://www.stata.com/help.cgi?search
*   http://www.stata.com/support/faqs/resources/statalist-faq/
*   http://www.ats.ucla.edu/stat/stata/


© Copyright 1996–2014 StataCorp LP   |   Terms of use   |   Privacy   |   Contact us   |   Site index