www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.bugs - [Bug 191] New: Cannot refer to member variable in default value for method parameter

reply d-bugmail puremagic.com writes:
http://d.puremagic.com/bugzilla/show_bug.cgi?id=191

           Summary: Cannot refer to member variable in default value for
                    method parameter
           Product: D
           Version: 0.160
          Platform: PC
        OS/Version: Windows
            Status: NEW
          Keywords: rejects-valid
          Severity: normal
          Priority: P3
         Component: DMD
        AssignedTo: bugzilla digitalmars.com
        ReportedBy: deewiant gmail.com


--
class c {
        int x = 5;
        int foo(int a = x) { return a; }
}

void main() {
        assert ((new c).foo() == 5);
}
--

The above code does not compile, producing the error "need 'this' to access
member x".

Obliging by changing "int a = x" to "int a = this.x" changes the error to
"'this' is only allowed in non-static member functions" followed by "this for x
needs to be type c not type int".

It can be worked around by removing the default value and manually overriding
foo with "int foo() { return foo(x); }", but this is a needless annoyance and
precisely what default values are there for.

It might be worth noting that this is a longstanding bug which bit me for the
first time with DMD 0.128:
http://www.digitalmars.com/d/archives/digitalmars/D/bugs/4532.html


-- 
Jun 11 2006
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
<d-bugmail puremagic.com> wrote in message news:bug-191-

 It might be worth noting that this is a longstanding bug which bit me for 
 the
 first time with DMD 0.128:
 http://www.digitalmars.com/d/archives/digitalmars/D/bugs/4532.html

Is this even in the spec? I had no idea you were supposed to be allowed to use members as default parameters.
Jun 11 2006
parent reply Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Jarrett Billingsley wrote:
 Is this even in the spec?  I had no idea you were supposed to be allowed to 
 use members as default parameters. 
 

All I can find about default parameters in the spec is at the "Functions" page, where they pop out of the blue in the phrase "A function parameter's default value is not inherited", which is all that's said about them. The changelog for DMD 0.92 also says "Added default arguments to function parameters. Semantics are like C++." but that's it. After some testing, it seems that that would actually explain it: C++ doesn't allow it. For what it's worth, I don't see any sense in this restriction - how is a class's member different from any other variable in this situation? Especially since the workaround is so simple, and isn't that all that default parameters are meant to do - overload the method into a form which takes one less parameter and passes the default value to the original? They don't have to implement it that way, but that's how I see the semantics of it. And I'm not a compiler writer, but it doesn't seem very difficult to implement either.
Jun 11 2006
next sibling parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Deewiant" <deewiant.doesnotlike.spam gmail.com> wrote in message 
news:e6hp54$a2d$1 digitaldaemon.com...

 For what it's worth, I don't see any sense in this restriction - how is a
 class's member different from any other variable in this situation?

Oh, wow, I didn't know that it allowed you to do this with other kinds of variables. I always thought that the default parameter initializer had to evaluate to a constant!
 Especially
 since the workaround is so simple, and isn't that all that default 
 parameters
 are meant to do - overload the method into a form which takes one less 
 parameter
 and passes the default value to the original?

Well, as far as I know, that's not how the compiler implements it. It just keeps one copy of the function, and when it comes across a call that doesn't have all the parameters, it rewrites the function call so that the unwritten parameters use the parameter initializer. So there's no actual overloading going on.
 They don't have to implement it
 that way, but that's how I see the semantics of it.

That might be the problem - depending on whether or not the default argument was a member variable, it might end up adding complexity to the default argument mechanism, since the value of a local variable can't really be determined until _after_ the function has been called with a 'this' reference. It would be like trying to do something like: void func(int[] x, int y = x[5]) Since x[5] can't be determined until after func() has been called, it would have to have multiple code paths for func; one which would be for taking a y, and one which would determine y from the x argument. Though it could possibly be added into the current mechanism, if you had a class like: class A { int x; void func(int y = x) { writefln(y); } } Then if you called func: A a = new A; a.func(); The compiler would know that the default initializer for the first argument is the member variable x, and would then rewrite the call as: a.func(a.x); Possible, but kind of complicated. Overloading a function to implement the default parameter with a member variable as the initializer seems like a simple enough workaround until (if?) this is fixed. I agree that it should be possible for consistency with the ability to use other variables as default argument initializers, but I don't know how many extra levels of complexity it adds.
Jun 11 2006
parent Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Jarrett Billingsley wrote:
 "Deewiant" <deewiant.doesnotlike.spam gmail.com> wrote in message 
 news:e6hp54$a2d$1 digitaldaemon.com...
 Especially since the workaround is so simple, and isn't that all that
 default parameters are meant to do - overload the method into a form which
 takes one less parameter and passes the default value to the original?

Well, as far as I know, that's not how the compiler implements it. It just keeps one copy of the function, and when it comes across a call that doesn't have all the parameters, it rewrites the function call so that the unwritten parameters use the parameter initializer. So there's no actual overloading going on.

If it does it like that - which _would_ be simpler and possibly faster for the non-class-member case - I can sort of see why it might be tricky for the class-member case. But it's still not impossible, like you pointed out: calling a method, the compiler knows which class instance's method is being called and can take the member from the same instance. It does, however, become impossible if the member is private or protected, and the caller can't access the variable. Which, come to think of it, is probably why it's not allowed in C++. Simulating that extra overloaded method does work even in that case, though.
Jun 11 2006
prev sibling next sibling parent reply BCS <BCS pathlink.com> writes:
Deewiant wrote:
 Jarrett Billingsley wrote:
 
Is this even in the spec?  I had no idea you were supposed to be allowed to 
use members as default parameters. 

All I can find about default parameters in the spec is at the "Functions" page, where they pop out of the blue in the phrase "A function parameter's default value is not inherited", which is all that's said about them. The changelog for DMD 0.92 also says "Added default arguments to function parameters. Semantics are like C++." but that's it.

Is this correct behavior? If it is it needs to be documeted better. <code> import std.stdio; class C { int foo(int x = 1){return x;} } class D:C { int foo(int x = 2){return x;} } void main() { C c = new D; D d = cast(D)c; writef(c.foo,\n); // prints 1 (?) writef(d.foo,\n); // prints 2 } </code>
Jun 12 2006
next sibling parent Oskar Linde <oskar.lindeREM OVEgmail.com> writes:
BCS skrev:
 Deewiant wrote:
 Jarrett Billingsley wrote:

 Is this even in the spec?  I had no idea you were supposed to be 
 allowed to use members as default parameters.

All I can find about default parameters in the spec is at the "Functions" page, where they pop out of the blue in the phrase "A function parameter's default value is not inherited", which is all that's said about them. The changelog for DMD 0.92 also says "Added default arguments to function parameters. Semantics are like C++." but that's it.

Is this correct behavior? If it is it needs to be documeted better. <code> import std.stdio; class C { int foo(int x = 1){return x;} } class D:C { int foo(int x = 2){return x;} } void main() { C c = new D; D d = cast(D)c; writef(c.foo,\n); // prints 1 (?) writef(d.foo,\n); // prints 2 } </code>

I guess this is the intended behavior. The default argument is evaluated at call site and passed to the function. main() calling c.foo has no way of knowing (at compile time) what derived class instance c may hold (there is no vtbl for default arguments). I see how this can lead to hard to find bugs. I.e. if the default argument is changed in the base class, but not in a (maybe independently developed) derived class. It may be better if D inherited default values and forbade them to be redefined in derived classes. /Oskar
Jun 12 2006
prev sibling parent reply Bruno Medeiros <brunodomedeirosATgmail SPAM.com> writes:
BCS wrote:
 Deewiant wrote:
 Jarrett Billingsley wrote:

 Is this even in the spec?  I had no idea you were supposed to be 
 allowed to use members as default parameters.

All I can find about default parameters in the spec is at the "Functions" page, where they pop out of the blue in the phrase "A function parameter's default value is not inherited", which is all that's said about them. The changelog for DMD 0.92 also says "Added default arguments to function parameters. Semantics are like C++." but that's it.

Is this correct behavior? If it is it needs to be documeted better. <code> import std.stdio; class C { int foo(int x = 1){return x;} } class D:C { int foo(int x = 2){return x;} } void main() { C c = new D; D d = cast(D)c; writef(c.foo,\n); // prints 1 (?) writef(d.foo,\n); // prints 2 } </code>

Hum, quite a catch you got there! Not only should it be better documented, but maybe it shouldn't work this way. Perhaps the default values should be set not at the call site, but by the function itself (thus depending on it's run-time type, and not it's compile-time type), or maybe it could be as Oskar's idea ("if D inherited default values and forbade them to be redefined in derived classes") -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jun 13 2006
next sibling parent BCS <BCS pathlink.com> writes:
Bruno Medeiros wrote:
 BCS wrote:
 <code>
 import std.stdio;

 class C        { int foo(int x = 1){return x;} }
 class D:C    { int foo(int x = 2){return x;} }


 void main()
 {
     C c = new D;
     D d = cast(D)c;

     writef(c.foo,\n);    // prints 1 (?)
     writef(d.foo,\n);    // prints 2
 }
 </code>

Hum, quite a catch you got there! Not only should it be better documented, but maybe it shouldn't work this way. Perhaps the default values should be set not at the call site, but by the function itself (thus depending on it's run-time type, and not it's compile-time type), or maybe it could be as Oskar's idea ("if D inherited default values and forbade them to be redefined in derived classes")

// never call with i set explicitly class C { abstract void doSomthing(char[] msg, int i=0); } class D:C { abstract void doSomthing(char[] msg, int i=1); } class E:D { abstract void doSomthing(char[] msg, int i=2); } class F:E { void doSomthing(char[] msg, int i=3) { writef(`doSomthing("%s") as class %c`\n,msg, 'C'+i); } } Now we known the type of the reference used to call doSomthing. (I pray I never have reason to use this.)
Jun 13 2006
prev sibling parent reply Don Clugston <dac nospam.com.au> writes:
Bruno Medeiros wrote:
 BCS wrote:
 Deewiant wrote:
 All I can find about default parameters in the spec is at the 
 "Functions" page,
 where they pop out of the blue in the phrase "A function parameter's 
 default
 value is not inherited", which is all that's said about them. The 




documented, but maybe it shouldn't work this way. Perhaps the default values should be set not at the call site, but by the function itself (thus depending on it's run-time type, and not it's compile-time type),

I'm almost certain that default values should work that way. They should just be syntactic sugar for overloaded functions. Right now, they have the same problem which C++ has -- there's no way that a template can find out what the default values are, and the default values can't be used by function pointers or delegates. So for example, void foo(int a, int b=2, int c=3) {} void goo(int a, int b, int c) { } void goo(int a, int b) { goo(a, b, 3); } void goo(int a) { goo(a, 2, 3); } void function(int) boo; foo(7); // ok goo(7); // ok boo = goo; // ok boo(5); boo = foo; // doesn't compile -- why not? boo(5); or maybe it could be as Oskar's idea ("if D
 inherited default values and forbade them to be redefined in derived 
 classes")

Jun 14 2006
parent reply Bruno Medeiros <brunodomedeirosATgmail SPAM.com> writes:
Don Clugston wrote:
 Bruno Medeiros wrote:
 BCS wrote:
 Deewiant wrote:
 All I can find about default parameters in the spec is at the 
 "Functions" page,
 where they pop out of the blue in the phrase "A function parameter's 
 default
 value is not inherited", which is all that's said about them. The 




documented, but maybe it shouldn't work this way. Perhaps the default values should be set not at the call site, but by the function itself (thus depending on it's run-time type, and not it's compile-time type),

I'm almost certain that default values should work that way. They should just be syntactic sugar for overloaded functions. Right now, they have the same problem which C++ has -- there's no way that a template can find out what the default values are, and the default values can't be used by function pointers or delegates. So for example, void foo(int a, int b=2, int c=3) {} void goo(int a, int b, int c) { } void goo(int a, int b) { goo(a, b, 3); } void goo(int a) { goo(a, 2, 3); } void function(int) boo; foo(7); // ok goo(7); // ok boo = goo; // ok boo(5); boo = foo; // doesn't compile -- why not? boo(5);

You meant: boo = &goo; // ok boo(5); boo = &foo; // doesn't compile -- why not? Anyway, "Why not?" ? How could that compile? Even with that syntatic sugar the problem would subsist: remember that the expression (&foo) has to be evaluable by itself, and if there are many overloads of foo there is no way (with that syntax at least) to choose the correct one. (DMD currently chooses the lexically first overload) -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jun 17 2006
next sibling parent reply Don Clugston <dac nospam.com.au> writes:
Bruno Medeiros wrote:
 Don Clugston wrote:
 Bruno Medeiros wrote:
 BCS wrote:
 Deewiant wrote:
 All I can find about default parameters in the spec is at the 
 "Functions" page,
 where they pop out of the blue in the phrase "A function 
 parameter's default
 value is not inherited", which is all that's said about them. The 




documented, but maybe it shouldn't work this way. Perhaps the default values should be set not at the call site, but by the function itself (thus depending on it's run-time type, and not it's compile-time type),

I'm almost certain that default values should work that way. They should just be syntactic sugar for overloaded functions. Right now, they have the same problem which C++ has -- there's no way that a template can find out what the default values are, and the default values can't be used by function pointers or delegates. So for example, void foo(int a, int b=2, int c=3) {} void goo(int a, int b, int c) { } void goo(int a, int b) { goo(a, b, 3); } void goo(int a) { goo(a, 2, 3); } void function(int) boo; foo(7); // ok goo(7); // ok boo = goo; // ok boo(5); boo = foo; // doesn't compile -- why not? boo(5);

You meant: boo = &goo; // ok boo(5); boo = &foo; // doesn't compile -- why not? Anyway, "Why not?" ? How could that compile? Even with that syntatic sugar the problem would subsist: remember that the expression (&foo) has to be evaluable by itself, and if there are many overloads of foo there is no way (with that syntax at least) to choose the correct one. (DMD currently chooses the lexically first overload)

But the 'goo' example does compile. Why is there a difference between 'goo' and 'foo'? AFAIK, default parameters were introduced (in C++) purely as syntactic sugar, and AFAIK they are only ever used in that way. I think they should be just syntactic sugar. As they exist in both D and C++, they are a quirky language primitive which cannot be emulated in any other way. They lead to some horrible corner cases.
Jun 23 2006
parent Bruno Medeiros <brunodomedeirosATgmail SPAM.com> writes:
Don Clugston wrote:
 Bruno Medeiros wrote:
 Don Clugston wrote:
 Bruno Medeiros wrote:
 BCS wrote:
 Deewiant wrote:
 All I can find about default parameters in the spec is at the 
 "Functions" page,
 where they pop out of the blue in the phrase "A function 
 parameter's default
 value is not inherited", which is all that's said about them. The 




documented, but maybe it shouldn't work this way. Perhaps the default values should be set not at the call site, but by the function itself (thus depending on it's run-time type, and not it's compile-time type),

I'm almost certain that default values should work that way. They should just be syntactic sugar for overloaded functions. Right now, they have the same problem which C++ has -- there's no way that a template can find out what the default values are, and the default values can't be used by function pointers or delegates. So for example, void foo(int a, int b=2, int c=3) {} void goo(int a, int b, int c) { } void goo(int a, int b) { goo(a, b, 3); } void goo(int a) { goo(a, 2, 3); } void function(int) boo; foo(7); // ok goo(7); // ok boo = goo; // ok boo(5); boo = foo; // doesn't compile -- why not? boo(5);

You meant: boo = &goo; // ok boo(5); boo = &foo; // doesn't compile -- why not? Anyway, "Why not?" ? How could that compile? Even with that syntatic sugar the problem would subsist: remember that the expression (&foo) has to be evaluable by itself, and if there are many overloads of foo there is no way (with that syntax at least) to choose the correct one. (DMD currently chooses the lexically first overload)

But the 'goo' example does compile. Why is there a difference between 'goo' and 'foo'?

What I was trying to say, in the general sense, is that by making default parameters be just sugar for function overloads, we become susceptible to the problems of ambiguous homonymous function overloads (which in fact, is a separate issue, and the one I was more worried about) I'm not saying default parameters shouldn't be syntactic sugar because of that, I'm just pointing it out. As for the ambiguous overloads issue: in the example above, I said "DMD currently chooses the lexically first overload" but that is not entirely correct. If that was so, then: boo = &goo; // ok would not work as in the example above, yet it does. This can only be an ugly special case hack, and a huge breach of consistency, since the following doesn't work: (&goo) (1, 2); // Expected func with 2 params auto temp = &goo; boo = temp; // Type mismatch, expected void function(int) This worries me.
 AFAIK, default parameters were introduced (in C++) purely as syntactic 
 sugar, and AFAIK they are only ever used in that way. I think they 
 should be just syntactic sugar. As they exist in both D and C++, they 
 are a quirky language primitive which cannot be emulated in any other 
 way. They lead to some horrible corner cases.

Yes, like that problem with overloading default parameters. -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jun 23 2006
prev sibling parent BCS <BCS pathlink.com> writes:
Bruno Medeiros wrote:
 Don Clugston wrote:
 
 Bruno Medeiros wrote:


 I'm almost certain that default values should work that way. They 
 should just be syntactic sugar for overloaded functions.
 Right now, they have the same problem which C++ has -- there's no way 
 that a template can find out what the default values are, and the 
 default values can't be used by function pointers or delegates.
 So for example,

 void foo(int a, int b=2, int c=3) {}

 void goo(int a, int b, int c) { }
 void goo(int a, int b) { goo(a, b, 3); }
 void goo(int a) { goo(a, 2, 3); }

 void function(int) boo;

 foo(7); // ok
 goo(7); // ok

 boo = goo; // ok
 boo(5);

 boo = foo; // doesn't compile -- why not?
 boo(5);

You meant: boo = &goo; // ok boo(5); boo = &foo; // doesn't compile -- why not? Anyway, "Why not?" ? How could that compile? Even with that syntatic sugar the problem would subsist: remember that the expression (&foo) has to be evaluable by itself, and if there are many overloads of foo there is no way (with that syntax at least) to choose the correct one. (DMD currently chooses the lexically first overload)

Things get worse when auto is used. you end up with a type that is indeterminable without knowing the lexical order of function decelerations. I that case code validity is dependent on the order of other code. int foo(long i, int j){return i+j;} char[] foo(int i){return i;} auto fp = &foo; auto i = foo(0,0); // legal unless foo's are reversed What is need is a way to specify a particular function as a pointer. auto fp1 = &foo(int); auto fp2 = &foo(long,int); // function don't run assert(fp1 != fp2);
Jun 23 2006
prev sibling parent reply Bruno Medeiros <brunodomedeirosATgmail SPAM.com> writes:
Deewiant wrote:
 Jarrett Billingsley wrote:
 Is this even in the spec?  I had no idea you were supposed to be allowed to 
 use members as default parameters. 

All I can find about default parameters in the spec is at the "Functions" page, where they pop out of the blue in the phrase "A function parameter's default value is not inherited", which is all that's said about them. The changelog for DMD 0.92 also says "Added default arguments to function parameters. Semantics are like C++." but that's it. After some testing, it seems that that would actually explain it: C++ doesn't allow it.

Which then means this is not a bug. What shall it be now? (dropped issue?, documentation clarification request?, enhancement request?) -- Bruno Medeiros - CS/E student http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D
Jun 12 2006
parent Deewiant <deewiant.doesnotlike.spam gmail.com> writes:
Bruno Medeiros wrote:
 Deewiant wrote:
 Jarrett Billingsley wrote:
 Is this even in the spec?  I had no idea you were supposed to be allowed
 to use members as default parameters.

All I can find about default parameters in the spec is at the "Functions" page, where they pop out of the blue in the phrase "A function parameter's default value is not inherited", which is all that's said about them. The changelog for DMD 0.92 also says "Added default arguments to function parameters. Semantics are like C++." but that's it. After some testing, it seems that that would actually explain it: C++ doesn't allow it.

Which then means this is not a bug. What shall it be now? (dropped issue?, documentation clarification request?, enhancement request?)

If you ask me, it's both of the latter. The documentation certainly needs improvement in this regard, and I certainly want to be able to use class members as default parameters. <g>
Jun 12 2006