www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - [Suggestion] A few small improvements to variadic functions

reply Stewart Gordon <Stewart_member pathlink.com> writes:
1.  http://www.digitalmars.com/d/function.html#variadic

"To protect against the vagaries of stack layouts on different CPU 
architectures, use std.stdarg to access the variadic arguments: "

By the looks of it, you show us how to access variadic arguments by 
incrementing _argptr, but then tell us that the method isn't portable 
and that we should use std.stdarg.

Ever since I discovered stdarg.h in C I've thought it ridiculous, as 
it means that C isn't self-contained.  If the ability to declare a 
variadic function is built in, so should the means of accessing the 
arguments.

Having invented a system for variadicity that's built in and 
typesafe, it ought to at least be portable.

My idea is to give D-linkage variadic functions one more implicit 
parameter, void*[] _argList, which points to each argument's data.

Then the example code would become:

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

if (_arguments[i] == typeid(int))
{
int j = *cast(int*) _argList[i];
printf("\t%d\n", j);
}
else if (_arguments[i] == typeid(long))
{
long j = *cast(long*) _argList[i];
printf("\t%lld\n", j);
}
else if (_arguments[i] == typeid(double))
{
double d = *cast(double*) _argList[i];
printf("\t%g\n", d);
}
else if (_arguments[i] == typeid(FOO))
{
FOO f = *cast(FOO*) _argList[i];
printf("\t%p\n", f);
}
else {
assert(0);
}
}
}

----------

2.  There ought to be a way of passing the arglist of one variadic 
function directly to another.

The simple solution, probably thought of by many, would be to allow 
the token '...' to be passed in.  Example:

void writeMultiple(int count, ...) {
for (int i = 0; i < count; i++) writefln(...);
}

Further, I suppose the '...' passed to the function shouldn't have to 
be the whole '...' of the callee.  For example, this might just as 
well be valid:

void printError(char[] formatStr, ...) {
writefln("Error: " ~ formatStr, ...);
}

or equivalently

void printError(...) {
writefln("Error: ", ...);
}

or possibly even

void printError(...) {
writefln("The error ", ..., " has occurred");
}

(Should we allow slicing of '...'?  Hmm....)

Stewart.
Sep 29 2004
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"Stewart Gordon" <Stewart_member pathlink.com> wrote in message
news:cjee0g$1034$1 digitaldaemon.com...
 1.  http://www.digitalmars.com/d/function.html#variadic

 "To protect against the vagaries of stack layouts on different CPU
 architectures, use std.stdarg to access the variadic arguments: "

 By the looks of it, you show us how to access variadic arguments by
 incrementing _argptr, but then tell us that the method isn't portable
 and that we should use std.stdarg.

I agree that first example should be removed - just show people "the right way" from the start.
 Ever since I discovered stdarg.h in C I've thought it ridiculous, as
 it means that C isn't self-contained.  If the ability to declare a
 variadic function is built in, so should the means of accessing the
 arguments.

 Having invented a system for variadicity that's built in and
 typesafe, it ought to at least be portable.

 My idea is to give D-linkage variadic functions one more implicit
 parameter, void*[] _argList, which points to each argument's data.

It would be nice but there isn't much difference to the user between writing int j = *cast(int*)_argList[i]; and int j = va_arg!(int)(_argptr); I suppose the _argList version lets you arbitrarily index the i^{th} parameter while the va_arg version only lets you access the parameters in order. It takes time to fill those pointers, though. The _arguments array can be determined at compile time so it doesn't add much overhead to the call (I don't know exactly how DMD create _arguments, though).
 Then the example code would become:

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

 if (_arguments[i] == typeid(int))
 {
 int j = *cast(int*) _argList[i];
 printf("\t%d\n", j);
 }
 else if (_arguments[i] == typeid(long))
 {
 long j = *cast(long*) _argList[i];
 printf("\t%lld\n", j);
 }
 else if (_arguments[i] == typeid(double))
 {
 double d = *cast(double*) _argList[i];
 printf("\t%g\n", d);
 }
 else if (_arguments[i] == typeid(FOO))
 {
 FOO f = *cast(FOO*) _argList[i];
 printf("\t%p\n", f);
 }
 else {
 assert(0);
 }
 }
 }

 ----------

 2.  There ought to be a way of passing the arglist of one variadic
 function directly to another.

The C way is to typically have a variadic version and a va_list version (eg printf and vprintf). That way you can pass va_lists around. In D the pattern would become void foo(...) { } void vfoo(TypeInfo[] arguments, va_list argptr) { } Such functions should be added to std.stdio (or just make writex public).
 The simple solution, probably thought of by many, would be to allow
 the token '...' to be passed in.  Example:

 void writeMultiple(int count, ...) {
 for (int i = 0; i < count; i++) writefln(...);
 }

 Further, I suppose the '...' passed to the function shouldn't have to
 be the whole '...' of the callee.  For example, this might just as
 well be valid:

 void printError(char[] formatStr, ...) {
 writefln("Error: " ~ formatStr, ...);
 }

 or equivalently

 void printError(...) {
 writefln("Error: ", ...);
 }

 or possibly even

 void printError(...) {
 writefln("The error ", ..., " has occurred");
 }

 (Should we allow slicing of '...'?  Hmm....)

 Stewart.

Sep 29 2004
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Ben Hinkle wrote:
<snip>
 My idea is to give D-linkage variadic functions one more implicit
 parameter, void*[] _argList, which points to each argument's data.

It would be nice but there isn't much difference to the user between writing int j = *cast(int*)_argList[i]; and int j = va_arg!(int)(_argptr);

Except for the absurdity of effectively importing a language feature. I thought half the point of the D vararg system was to be built in.
 I suppose the _argList version lets you arbitrarily index the i^{th}
 parameter while the va_arg version only lets you access the parameters in
 order. It takes time to fill those pointers, though.

It could be filled iff it's used.... <snip>
 2.  There ought to be a way of passing the arglist of one variadic
 function directly to another.

The C way is to typically have a variadic version and a va_list version (eg printf and vprintf). That way you can pass va_lists around.

To add my suggestion would protect us all from lib developers forgetting or not being bothered to do this. It would also be simple to do AFAICS.
 In D the pattern would become
  void foo(...) { }
  void vfoo(TypeInfo[] arguments, va_list argptr) { }
 Such functions should be added to std.stdio (or just make writex public).

Is it allowed for the two versions to be called the same? Or does D reject such potential backward incompatibility, no matter how likely or not someone really is to want to output an array of TypeInfos followed by a va_list? Stewart.
Sep 29 2004
parent reply "Ben Hinkle" <bhinkle mathworks.com> writes:
"Stewart Gordon" <smjg_1998 yahoo.com> wrote in message
news:cjepmv$18ij$1 digitaldaemon.com...
 Ben Hinkle wrote:
 <snip>
 My idea is to give D-linkage variadic functions one more implicit
 parameter, void*[] _argList, which points to each argument's data.

It would be nice but there isn't much difference to the user between


  int j = *cast(int*)_argList[i];
 and
  int j = va_arg!(int)(_argptr);

Except for the absurdity of effectively importing a language feature. I thought half the point of the D vararg system was to be built in.

true enough. Walter does prefer language support over standard library support. I don't think I'd say the current scheme is "absurd", though.
 I suppose the _argList version lets you arbitrarily index the i^{th}
 parameter while the va_arg version only lets you access the parameters


 order. It takes time to fill those pointers, though.

It could be filled iff it's used.... <snip>
 2.  There ought to be a way of passing the arglist of one variadic
 function directly to another.

The C way is to typically have a variadic version and a va_list version


 printf and vprintf). That way you can pass va_lists around.

To add my suggestion would protect us all from lib developers forgetting or not being bothered to do this. It would also be simple to do AFAICS.

I suppose - though I don't exactly know how the mechanism would work. For example would it be compatible with extern(C) variadic declarations: extern (C) printf(char* str, ...); void foo(char* str,...) { printf(str,...); } Does the body of foo copy the inputs to foo from foo's stack frame so that the printf stack frame sees them in the right place? One benefit of the va_list declarations is that it is obvious a pointer is being passed around instead of stack frames being copied.
 In D the pattern would become
  void foo(...) { }
  void vfoo(TypeInfo[] arguments, va_list argptr) { }
 Such functions should be added to std.stdio (or just make writex


 <snip>

 Is it allowed for the two versions to be called the same?

Do you mean instead of void foo(...) { } void vfoo(TypeInfo[] arguments, va_list argptr) { } have void foo(...) { } void foo(TypeInfo[] arguments, va_list argptr) { } The problem is that there is an ambiguity since the code foo(_arguments, _argptr) could match either one of the two functions. According to D's overloading rules it would error.
 Or does D
 reject such potential backward incompatibility, no matter how likely or
 not someone really is to want to output an array of TypeInfos followed
 by a va_list?

Where is the backward incompatibility? I don't understand the problem. Can you give an example?
Sep 29 2004
next sibling parent Stewart Gordon <smjg_1998 yahoo.com> writes:
Ben Hinkle wrote:
<snip>
 I suppose - though I don't exactly know how the mechanism would work.
 For example would it be compatible with extern(C) variadic
 declarations:

Good question. But probably.
  extern (C) printf(char* str, ...);
  void foo(char* str,...) { printf(str,...); }
 Does the body of foo copy the inputs to foo from foo's stack frame so
 that the printf stack frame sees them in the right place? One benefit
 of the va_list declarations is that it is obvious a pointer is being
 passed around instead of stack frames being copied.

Good question. Maybe Walter or someone could comment.... <snip>
 Do you mean instead of
   void foo(...) { }
   void vfoo(TypeInfo[] arguments, va_list argptr) { }
 have
   void foo(...) { }
   void foo(TypeInfo[] arguments, va_list argptr) { }
 The problem is that there is an ambiguity since the code
  foo(_arguments, _argptr)
 could match either one of the two functions. According to D's overloading
 rules it would error.

That's half what I said. But does the spec indicate anywhere that matching '...' is classed as an exact match?
 Or does D reject such potential backward incompatibility, no matter 
 how likely or not someone really is to want to output an array of 
 TypeInfos followed by a va_list?

Where is the backward incompatibility? I don't understand the problem. Can you give an example?

Suppose at first only void foo(...) () is defined. And then someone tries calling it, meaning this form, with a TypeInfo[] and a va_list. And then in a later version of the lib, void foo(TypeInfo[] arguments, va_list argptr) {} is defined. Then the effect of the call changes. Stewart.
Sep 30 2004
prev sibling parent pragma <pragma_member pathlink.com> writes:
In article <cjf0hd$1chb$1 digitaldaemon.com>, Ben Hinkle says...
Do you mean instead of
  void foo(...) { }
  void vfoo(TypeInfo[] arguments, va_list argptr) { }
have
  void foo(...) { }
  void foo(TypeInfo[] arguments, va_list argptr) { }
The problem is that there is an ambiguity since the code
 foo(_arguments, _argptr)
could match either one of the two functions. According to D's overloading
rules it would error.

Just a thought: this is another one of D's quirks that could use some attention. Suppose if the variadic function signatures were given the *lowest* priority for matching function overloads? At the very least, it would be consistent with how D matches individual parameters where the specific case is investigated first. version(DNG) pragma(EricAnderton,"yahoo");
Sep 30 2004