Thread overview
Default Argument Improvements?
Jan 31, 2007
Brian Byrne
Feb 01, 2007
Whyn Oop
Feb 02, 2007
Brian Byrne
Feb 02, 2007
Frits van Bommel
Feb 02, 2007
Brian Byrne
Feb 02, 2007
Whyn Oop
Feb 04, 2007
Brian Byrne
January 31, 2007
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
February 01, 2007
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)
- ...




February 02, 2007
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

February 02, 2007
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 ;).
February 02, 2007
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
February 02, 2007
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);


February 04, 2007
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