www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Default Argument Improvements?

reply Brian Byrne <bdbyrne wisc.edu> writes:
Hello,

One of the things that annoyed me in C++ was when I had a base class
constructor with which I wanted to supply default argument(s) for, but then
have a derived class with one or more extended parameters (no default value) on
top of the base's parameters. It's impossible to append the derived class's new
parameter(s) to the end of the base's while keeping the same order since the
original default parameter is wrenched in there. Example:

class A {
    this( int x, int y = 0 ) {
        ...
    }
}

class B : A {
    this( int x, int y, int z ) { // Same parameter order
        super( x, y );
        ...
    }
    this( int x, int z ) { // Requires duplication of function
        super( x );        // to get default argument effect
        ...
    }
}



Of course this applies in the general case not limited to member constructors.
Is there a reason that default arguments must be at the end of a function? Why
not allow:

void foo( int a, int b = 0, int c ) { ... }

Which could be called by:

foo( 5, , 10 ); // empty gap for default
foo( 5, void, 10 ); // or more explicit
foo( 5, ..., 10 ); // or can the ellipsis be unambiguously used?

To extend it even further, what if I had foo overloaded:

void foo( int a, real b = 1.5, int c ) { ... }

Then the above calling methods would be problematic. Could inserting the type
in the calling statement be used to disambiguate the functions?

foo( 5, int, 10 ); // matches foo(int, int, int); a = 5, b = 0, c = 10
foo( 0, real, 20 ); // matches foo(int, real, int); a = 0, b = 1.5, c = 20

and likewise going back to just having the first foo function:
foo( 5, auto, 10 ); // matches foo(int, int, int)



From my starting example with classes A and B, I wouldn't be helped much in
filling out parameter y in B's constructor. But maybe something along these
lines could work?:

class B : A {
    // This part is probably really, really broken,.. but the idea exists
    const int DEF_Y = ParameterTypeTuple!( super.this )[ 1 ].init;

    this( int x, int y = DEF_Y, int z ) { // Same parameter order
        super( x, y );
        ...
    }
}


This needs a lot more thought than I put into it, but if anything I'd just like
to know why mid-list default arguments aren't supported. I'm sure the reason is
probably trivial and I overlooked it. :)

Thanks,
Brian Byrne
Jan 30 2007
parent reply Whyn Oop <whyn oop.er> writes:
Brian Byrne Wrote:
 I'm sure the reason is probably trivial and I overlooked it. :)

The reason might be as trivial as run time complexity: I assume that such improvements would bring the compiler at least much closer to the class of problems, which are believed to not be solvable in polynomial runtime. This in turn would largely disable any maintainabilty. Please note, that currently every defaulted parameter actually is a shorthand, which frees the coder from the work of writing one more signature. Allowing defaulted parameters everywhere would free the coder from writing about half of the otherwise required signatures for each of those parameters. These further request can be forseen: - not beeing forced to assign to the i-th defaulted parameter by mentioning all previous defined deafulted parameters. I.e. something like: f( f.default[9]=2) instead of f( int, int ,int , int ,real, int,double, float, char, 2) - not being forced to know the exact position of a defaulted parameter by naming them explicitely. I.e. something like f( f.default.x=2) instead of f( f.default[9]=2) - ...
Feb 01 2007
parent reply Brian Byrne <bdbyrne wisc.edu> writes:
Whyn Oop Wrote:
 The reason might be as trivial as run time complexity: I assume that such
 improvements would bring the compiler at least much closer to the class
 of problems, which are believed to not be solvable in polynomial runtime.

I can't imagine there being much added complexity considering the programmer is explicitly stating when he wants a default argument to be used. All the compiler has to do is perform a function lookup, being wary of any ambiguity, and inserting that default argument's value in the function call. Another [trivial] example could be for some given Vector4f class, Vector4f set( float in_x = float.nan, float in_y = float.nan, float in_z = float.nan, float in_w = float.nan ) { x = ( in_x == float.nan ) ? x : in_x; y = ( in_y == float.nan ) ? y : in_y; z = ( in_z == float.nan ) ? z : in_z; w = ( in_w == float.nan ) ? w : in_w; } Vector4f vec = Vector4f( 1.0, 2.0, 3.0, 4.0 ); vec.set( 100.0, , 300.0, 400.0 ); // vec == Vector4f( 100.0, 2.0, 300.0, 400.0 ) Handy, no? Brian Byrne
Feb 01 2007
next sibling parent reply Frits van Bommel <fvbommel REMwOVExCAPSs.nl> writes:
Brian Byrne wrote:
 Vector4f set( float in_x = float.nan, float in_y = float.nan, float in_z =
float.nan, float in_w = float.nan ) {
     x = ( in_x == float.nan ) ? x : in_x;
     y = ( in_y == float.nan ) ? y : in_y;
     z = ( in_z == float.nan ) ? z : in_z;
     w = ( in_w == float.nan ) ? w : in_w;
 }

Those assignments always set the variables to in_*. The funny thing about NaNs: all normal comparisons return false. Use std.math.isnan(in_x) instead of (in_x == float.nan) if you prefer code that works ;).
Feb 01 2007
parent Brian Byrne <bdbyrne wisc.edu> writes:
Frits van Bommel Wrote:
 Those assignments always set the variables to in_*.
 The funny thing about NaNs: all normal comparisons return false. Use 
 std.math.isnan(in_x) instead of (in_x == float.nan) if you prefer code 
 that works ;).

Ah yes, good call! Forgot about that. :) Thanks for the heads up, Brian Byrne
Feb 01 2007
prev sibling parent reply Whyn Oop <whyn oop.er> writes:
Brian Byrne Wrote:

 Handy, no?

Please, just describe informally how a maintainer would have to evaluate the meaning of such codelines: f(,,,4,2); f(,,4.2);
Feb 02 2007
parent Brian Byrne <bdbyrne wisc.edu> writes:
Whyn Oop Wrote:

 Brian Byrne Wrote:
 
 Handy, no?

Please, just describe informally how a maintainer would have to evaluate the meaning of such codelines: f(,,,4,2); f(,,4.2);

Well, I guess it largely depends on your coding style. It may be a good idea to explicitly state it in your code that you are going to use default parameters in this case. f( auto, auto, auto, 4, 2 ); f( auto, auto, 4.2 ); Similarity still exists, but the goal is much more explicit now. Brian Byrne
Feb 04 2007