www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Better syntax for varargs / variadic functions?

reply Bill Baxter <dnewsgroup billbaxter.com> writes:
The magic _arguments and _argptr really in variadic functions really 
bother me.

Why not:
1) let the user name the varargs list like
      void foo(int x, Args...)
    or
      void foo(int x, ...Args)

2) Let _arguments and _argptr simply be fields of that instead of 
'magic' variables so:
       _arguments   --> Args.types
       _argptr      --> Args.pointers

3) Make it so that users don't have to advance _argptrs themselves. 
Make it an array of pointers instead.  The compiler knows the types and 
their sizes, so there's no reason the user should have to mess with this.
So instead of this :
     long j = *cast(long *)_argptr;
     _argptr += long.sizeof;
You'd just do:
     long j = *cast(long *)_argptr[i];

4) Get rid of the explicit cast.  Again the compiler knows the types, so 
  it seems silly for me to have to explicitly cast the argument myself. 
  Best would be if it could look like just another array:
     long j = *_argvals[i];
But some sort of template call would be ok I suppose
     long j = *_argcast!(i);
    And of course really this should be a field of the named argument 
too. so Args.values[i].

Then we could have this version of the foo example:

void foo(int x, Args...)
{
     printf("%d arguments\n", Args.length);
     for (int i = 0; i < Args.length; i++)
     {   Args.types[i].print();

	if (Args.types[i] == typeid(int))
	{
	    int j = Args.values[i];
	    printf("\t%d\n", j);
	}
	else if (Args.types[i] == typeid(long))
	{
	    long j = Args.values[i];
	    printf("\t%lld\n", j);
	}
	else if (Args.types[i] == typeid(double))
	{
	    double d = Args.values[i];
	    printf("\t%g\n", d);
	}
	else if (Args.types[i] == typeid(FOO))
	{
	    FOO f = Args.values[i];
	    printf("\t%p\n", f);
	}
	else
	    assert(0);
     }
}

I think 1),2), and 3) are pretty straightforward, I don't see any major 
language issues for making those work.  Maybe you'll need ...Args 
instead of Args... to remain context free, but that'd be acceptable.

The argument against Number 3) may be that building the list of pointers 
would mean extra overhead for every varargs-using function.  But 
generally if an argument is passed in, somebody somewhere down the line 
is going to have to compute the address for it.  Might as well be the 
compiler behind the scenes.  The extra storage for those pointers is an 
extra overhead, but it seems unlikely to be that much.  It's just N 
extra pointers that can be put on the stack.  And the compiler can even 
optimize it out if Args.pointers[i] isn't used in that function.

Number 4) is tricky, though, without making a special case.  Generally 
somearray[i] can't return values of different types depending on i, so 
letting it do so would require that it be treated specially.  If there 
were someway to cast something to the type represented by a TypeInfo 
then you'd be able to use that.  But basically the only way to do that 
is with a big switch.

Even if 4 is impossible, I think having 1),2 ) and 3) would be nice. 
Then foo would look like:

void foo(int x, Args...)
{
     printf("%d arguments\n", Args.length);
     for (int i = 0; i < Args.length; i++)
     {   Args.types[i].print();

	if (Args.types[i] == typeid(int))
	{
	    int j = *cast(int*)Args.ptrs[i];
	    printf("\t%d\n", j);
	}
         ...

Which I still find to be quite an improvement.

But even better would be support for variadic templates which would let 
you avoid that 'if type == this {} else if type == that {} else if type 
== theother {} ...'.   Like here:

http://www.generic-programming.org/~dgregor/cpp/variadic-templates.html

--bb
Oct 04 2006
parent reply "Chris Miller" <chris dprogramming.com> writes:
How about this:

void foo(...)
{
    foreach(arg; variadic(_arguments, _argptr))
    {
       if(arg.type =3D=3D typeid(char[]))
       {
          char[] str;
          arg.next(str);
          printf("%.*s", str);
       }
       else if(arg.type =3D=3D typeid(int))
       {
          int i;
          arg.next(i);
          printf("%d", i);
       }
       else
       {
          assert(0);
          return;
       }
    }
}

http://www.dprogramming.com/docs/variadic/variadic.html
Oct 04 2006
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Much better!

It seems very odd, though, that the opApply doesn't do the advancing of 
arg (the iw++ that next() does).   If opApply took care of that, then 
get() or value() would probably be a better name than next().

Then if the compiler would just pass you one of these objects with a 
name you specify, all would be gravy:

void foo(...Args)
{
    foreach(arg; Args)
    {
       if(arg.type == typeid(char[]))
       {
          char[] str;
          arg.get(str);
          printf("%.*s", str);
       }
       else if(arg.type == typeid(int))
       {
          int i;
          arg.get(i);
          printf("%d", i);
       }
       else
       {
          assert(0);
          return;
       }
    }
}

Then we'd really be cooking with gas.

--bb
Oct 05 2006
parent reply "Chris Miller" <chris dprogramming.com> writes:
On Thu, 05 Oct 2006 04:41:38 -0400, Bill Baxter  
<dnewsgroup billbaxter.com> wrote:

 Much better!

 It seems very odd, though, that the opApply doesn't do the advancing of  
 arg (the iw++ that next() does).   If opApply took care of that, then  
 get() or value() would probably be a better name than next().
Because a lot of times some arguments mean "look at next argument too" so you'd only need to call next() again and it wouldn't goof up the iteration; you could also even foreach() over the same args for a sub range: foreach(arg; args){ foreach(arg; args){ if(foo)break; } }.
Oct 05 2006
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Chris Miller wrote:
 On Thu, 05 Oct 2006 04:41:38 -0400, Bill Baxter 
 <dnewsgroup billbaxter.com> wrote:
 
 Much better!

 It seems very odd, though, that the opApply doesn't do the advancing 
 of arg (the iw++ that next() does).   If opApply took care of that, 
 then get() or value() would probably be a better name than next().
Because a lot of times some arguments mean "look at next argument too" so you'd only need to call next() again and it wouldn't goof up the iteration; you could also even foreach() over the same args for a sub range: foreach(arg; args){ foreach(arg; args){ if(foo)break; } }.
Seems like there's no reason you can't have the best of both worlds. If you only call get(), then the pointer advances for you by one. Or if you use next(), then it doesn't. It seems skip() might be handy too. Attached is a version that has next(), get(), and skip(). I'm not sure what it will do with your foreach(){ foreach() } thing, though. Maybe it's a D thing, but to me if I see a "foreach(foo, bar) { }" my brain *really* wants to believe that foo will be magically set to refer to each item in bar, one by one. --bb
Oct 17 2006