www.digitalmars.com         C & C++   DMDScript  

D - More games with varargs.

reply Andy Friesen <andy ikagames.com> writes:
Awhile ago, I linked to the XL language website regarding its 
implementation of vararg functions. (that is, functions that accept an 
arbitrary number of arguments)  XL's solution is to resolve the call to 
a series of recursive calls, using an opaque symbol to represent the 
arguments.

The more I think about it, the more I get the feeling that such a symbol 
would have lots of really interesting uses.  I'll just call it 'other' 
for the moment. (it doesn't matter exactly what keyword you use.  '...' 
would be C-ish)

Basically, 'other' represents some unspecified arguments, each of which 
is some type or other. (this is purely a compile-time construct) We 
don't care exactly what.  What's important is how to pry arguments out 
of it:

void write() {
    // do nothing.  Or, if we want BASIC-like print statement behaviour
    // we could print a newline here.
}
void write(int i, other) {
    writeInt(i);
    write(other);
}
void write(float f, other) {
    writeFloat(f);
    write(other);
}

These functions (except the first) accept an argument, then some other 
junk that we don't care about just yet.  The latter two use the argument 
that we pried out, and pass the others along.  If 'other' is empty, then 
the first method matches, and the recursion ends.

So, now we've implemented typesafe functions that accept any number of 
arguments, each of an arbitrary type.  Score.  Additionally, this allows 
for exceptionally elegant min/max functions.

But this would also be handy for just about any time you want to wrap a 
callable object without concern for the number or types of arguments it 
accepts.

class FunctionPtrWrapper(T) {
    private T _func;
    this(T func) {
       _func = func;
    }

    // accept any arguments
    void opCall(other) {
       // try to cram them into the function pointer.
       // (this would be a compile-time error if the signatures
       //  don't match exactly)
       _func(other);
    }
}

alias void delegate(int, float, char[]) DelegateType;
alias void function(int, float, char[]) CFunctionPtr;

DelegateType theDelegate =
    new FunctionPtrWrapper!(CFunctionPtr) (&someGlobalFunction).opCall;

The biggest downside I can see is that we run into the same limitations 
as templates: the method source must be available to the compiler, and 
it would only be good for global or static methods.  A bit aggrivating, 
but still pretty nice.  Of course, this all assumes that implementating 
it in the compiler is realistic.

Okay, I've spent long enough writing this that I'm starting to second 
guess myself.  Better send it off before I convince myself to delete it.

  -- andy
Jan 07 2004
next sibling parent J Anderson <REMOVEanderson badmama.com.au> writes:
Andy Friesen wrote:

 Awhile ago, I linked to the XL language website regarding its 
 implementation of vararg functions. (that is, functions that accept an 
 arbitrary number of arguments)  XL's solution is to resolve the call 
 to a series of recursive calls, using an opaque symbol to represent 
 the arguments.

 The more I think about it, the more I get the feeling that such a 
 symbol would have lots of really interesting uses.  I'll just call it 
 'other' for the moment. (it doesn't matter exactly what keyword you 
 use.  '...' would be C-ish)

 Basically, 'other' represents some unspecified arguments, each of 
 which is some type or other. (this is purely a compile-time construct) 
 We don't care exactly what.  What's important is how to pry arguments 
 out of it:

 void write() {
    // do nothing.  Or, if we want BASIC-like print statement behaviour
    // we could print a newline here.
 }
 void write(int i, other) {
    writeInt(i);
    write(other);
 }
 void write(float f, other) {
    writeFloat(f);
    write(other);
 }

 These functions (except the first) accept an argument, then some other 
 junk that we don't care about just yet.  The latter two use the 
 argument that we pried out, and pass the others along.  If 'other' is 
 empty, then the first method matches, and the recursion ends.

 So, now we've implemented typesafe functions that accept any number of 
 arguments, each of an arbitrary type.  Score.  Additionally, this 
 allows for exceptionally elegant min/max functions.

 But this would also be handy for just about any time you want to wrap 
 a callable object without concern for the number or types of arguments 
 it accepts.

 class FunctionPtrWrapper(T) {
    private T _func;
    this(T func) {
       _func = func;
    }

    // accept any arguments
    void opCall(other) {
       // try to cram them into the function pointer.
       // (this would be a compile-time error if the signatures
       //  don't match exactly)
       _func(other);
    }
 }

 alias void delegate(int, float, char[]) DelegateType;
 alias void function(int, float, char[]) CFunctionPtr;

 DelegateType theDelegate =
    new FunctionPtrWrapper!(CFunctionPtr) (&someGlobalFunction).opCall;

 The biggest downside I can see is that we run into the same 
 limitations as templates: the method source must be available to the 
 compiler, and it would only be good for global or static methods.  A 
 bit aggrivating, but still pretty nice.  Of course, this all assumes 
 that implementating it in the compiler is realistic.

 Okay, I've spent long enough writing this that I'm starting to second 
 guess myself.  Better send it off before I convince myself to delete it.

  -- andy

I like it (particularly since I suggested something similar with templates).
Jan 07 2004
prev sibling next sibling parent davepermen <davepermen_member pathlink.com> writes:
nice and clean.

Walter, you like that?

In article <bthm67$2hcs$1 digitaldaemon.com>, Andy Friesen says...
Awhile ago, I linked to the XL language website regarding its 
implementation of vararg functions. (that is, functions that accept an 
arbitrary number of arguments)  XL's solution is to resolve the call to 
a series of recursive calls, using an opaque symbol to represent the 
arguments.

The more I think about it, the more I get the feeling that such a symbol 
would have lots of really interesting uses.  I'll just call it 'other' 
for the moment. (it doesn't matter exactly what keyword you use.  '...' 
would be C-ish)

Basically, 'other' represents some unspecified arguments, each of which 
is some type or other. (this is purely a compile-time construct) We 
don't care exactly what.  What's important is how to pry arguments out 
of it:

void write() {
    // do nothing.  Or, if we want BASIC-like print statement behaviour
    // we could print a newline here.
}
void write(int i, other) {
    writeInt(i);
    write(other);
}
void write(float f, other) {
    writeFloat(f);
    write(other);
}

These functions (except the first) accept an argument, then some other 
junk that we don't care about just yet.  The latter two use the argument 
that we pried out, and pass the others along.  If 'other' is empty, then 
the first method matches, and the recursion ends.

So, now we've implemented typesafe functions that accept any number of 
arguments, each of an arbitrary type.  Score.  Additionally, this allows 
for exceptionally elegant min/max functions.

But this would also be handy for just about any time you want to wrap a 
callable object without concern for the number or types of arguments it 
accepts.

class FunctionPtrWrapper(T) {
    private T _func;
    this(T func) {
       _func = func;
    }

    // accept any arguments
    void opCall(other) {
       // try to cram them into the function pointer.
       // (this would be a compile-time error if the signatures
       //  don't match exactly)
       _func(other);
    }
}

alias void delegate(int, float, char[]) DelegateType;
alias void function(int, float, char[]) CFunctionPtr;

DelegateType theDelegate =
    new FunctionPtrWrapper!(CFunctionPtr) (&someGlobalFunction).opCall;

The biggest downside I can see is that we run into the same limitations 
as templates: the method source must be available to the compiler, and 
it would only be good for global or static methods.  A bit aggrivating, 
but still pretty nice.  Of course, this all assumes that implementating 
it in the compiler is realistic.

Okay, I've spent long enough writing this that I'm starting to second 
guess myself.  Better send it off before I convince myself to delete it.

  -- andy

Jan 07 2004
prev sibling parent reply "C" <dont respond.com> writes:
Smooth , why would it not be good for member functions ?

C
"Andy Friesen" <andy ikagames.com> wrote in message
news:bthm67$2hcs$1 digitaldaemon.com...
 Awhile ago, I linked to the XL language website regarding its
 implementation of vararg functions. (that is, functions that accept an
 arbitrary number of arguments)  XL's solution is to resolve the call to
 a series of recursive calls, using an opaque symbol to represent the
 arguments.

 The more I think about it, the more I get the feeling that such a symbol
 would have lots of really interesting uses.  I'll just call it 'other'
 for the moment. (it doesn't matter exactly what keyword you use.  '...'
 would be C-ish)

 Basically, 'other' represents some unspecified arguments, each of which
 is some type or other. (this is purely a compile-time construct) We
 don't care exactly what.  What's important is how to pry arguments out
 of it:

 void write() {
     // do nothing.  Or, if we want BASIC-like print statement behaviour
     // we could print a newline here.
 }
 void write(int i, other) {
     writeInt(i);
     write(other);
 }
 void write(float f, other) {
     writeFloat(f);
     write(other);
 }

 These functions (except the first) accept an argument, then some other
 junk that we don't care about just yet.  The latter two use the argument
 that we pried out, and pass the others along.  If 'other' is empty, then
 the first method matches, and the recursion ends.

 So, now we've implemented typesafe functions that accept any number of
 arguments, each of an arbitrary type.  Score.  Additionally, this allows
 for exceptionally elegant min/max functions.

 But this would also be handy for just about any time you want to wrap a
 callable object without concern for the number or types of arguments it
 accepts.

 class FunctionPtrWrapper(T) {
     private T _func;
     this(T func) {
        _func = func;
     }

     // accept any arguments
     void opCall(other) {
        // try to cram them into the function pointer.
        // (this would be a compile-time error if the signatures
        //  don't match exactly)
        _func(other);
     }
 }

 alias void delegate(int, float, char[]) DelegateType;
 alias void function(int, float, char[]) CFunctionPtr;

 DelegateType theDelegate =
     new FunctionPtrWrapper!(CFunctionPtr) (&someGlobalFunction).opCall;

 The biggest downside I can see is that we run into the same limitations
 as templates: the method source must be available to the compiler, and
 it would only be good for global or static methods.  A bit aggrivating,
 but still pretty nice.  Of course, this all assumes that implementating
 it in the compiler is realistic.

 Okay, I've spent long enough writing this that I'm starting to second
 guess myself.  Better send it off before I convince myself to delete it.

   -- andy

Jan 07 2004
parent Andy Friesen <andy ikagames.com> writes:
C wrote:

 Smooth , why would it not be good for member functions ?
 
 C

It wouldn't work for member functions because you'd need to recompile these things a whole lot. Doing this with member functions means that the vtable semantics get butchered. (which, if I'm not mistaken, is why you can't declare nonstatic member functions within templates defined inside a class) Come to think of it, that could lead to quite a lot of generated code, if the function in question isn't inlined, or is nontrivially large. write(int, string, float, char, wchar[]) would call write(string, float, char, wchar[]), which would in turn call write(float, char, wchar[]) and so forth. If it gets inlined, then it's not a big deal, but each of these functions would be made for exactly one invokation if not. eep. In my write() example, I had simply relayed the write call to some other normal function, but I don't think it's very reasonable for the compiler to be able to un-inline (outline?) some arbitrary part of a function. (this is what I get for pulling an idea from a language that uses inferred typing to implement generics) Maybe the solution is to just leave it as it is, and strongly suggest that such functions do as little actual work as possible, relaying to a helper that does the actual work. A less powerful implementation that works pretty much the same way could be to directly translate the call into a single call for each individual argument. The lack of explicit recursion and pattern matching (matching each argument's type using typical overloading rules) casterates it some, but leaves it plenty useful for things like a typesafe printf(). This would also not require that code be generated specifically for each invokation, which would make it realistic to use them as member functions. -- andy
Jan 07 2004