www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Function pointers/delegates default args were stealth removed?

reply Manu <turkeyman gmail.com> writes:
I just updated to 2.60 and found errors throughout my code where function
pointers default args no longer work.
*Every single project* I've written in D, 5 projects, don't work anymore,
including my projects at work.

I found this discussion: http://d.puremagic.com/issues/show_bug.cgi?id=3866
It seems the change was just decided and implemented with basically no
discussion or argument at all :/

My use cases are dynamic linkage, and cross-language integration.
I can't manually interact with DLL's containing API's that expect to have
default arguments if function pointers no longer support them.
Also when receiving foreign language function pointers, they frequently
need to have default args too.

I also integrate with many C style API's (rendering engines and the like),
which involve registration of various callbacks, and lots of those have
default args too.

I find this particularly surprising, since I recently motivated
implementation of new traits which could parse default args from parameter
lists, and use that to generation function pointers in templates which
auto-magically clone functions parameter lists verbatim, specifically
including the default args...
Aug 26 2012
next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
I don't think there's any cases where the change can't be worked 
around relatively easily. Correct me if I'm wrong.

I was also surprised to see this removed without mention, but 
after reading the bug, it seems like the correct decision. I 
don't like the idea of adding default arguments to types.
Aug 26 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 3:26 PM, Manu wrote:
 I just updated to 2.60 and found errors throughout my code where function
 pointers default args no longer work.
 _Every single project_ I've written in D, 5 projects, don't work anymore,
 including my projects at work.

 I found this discussion: http://d.puremagic.com/issues/show_bug.cgi?id=3866
 It seems the change was just decided and implemented with basically no
 discussion or argument at all :/

 My use cases are dynamic linkage, and cross-language integration.
 I can't manually interact with DLL's containing API's that expect to have
 default arguments if function pointers no longer support them.
 Also when receiving foreign language function pointers, they frequently need to
 have default args too.

 I also integrate with many C style API's (rendering engines and the like),
which
 involve registration of various callbacks, and lots of those have default args
too.

 I find this particularly surprising, since I recently motivated implementation
 of new traits which could parse default args from parameter lists, and use that
 to generation function pointers in templates which auto-magically clone
 functions parameter lists verbatim, specifically including the default args...
The trouble is that, as 3866 shows, there is no design anyone could come up with that worked in a consistent manner. The only consistent way out was to make default arguments a characteristic of the declaration, not of the type. The trouble for function pointers, is that any default args would need to be part of the type, not the declaration. I know it broke code (for many others, too), and I'm very sorry about that, but I don't see another way out. (Many uses of default arguments can be replaced with overloaded functions.)
Aug 26 2012
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Walter Bright:

 The trouble is that, as 3866 shows, there is no design anyone 
 could come up with that worked in a consistent manner. The only 
 consistent way out was to make default arguments a 
 characteristic of the declaration, not of the type.
I think Ada is doing what D does now, it doesn't accept default arguments for things like function pointers/delegates. Bye, bearophile
Aug 26 2012
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
Aug 26 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
Aug 26 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 02:44 AM, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable?
Yes.
 Consider what you do with a function pointer - you pass it to someone
 else. That someone else gets it as a type, not a declaration.
If it is a template function, yes. But then you may as well pass the function pointer variable per alias, which is common. Otherwise someone else gets nothing but a parameter declaration.
 I.e. you lose the default argument information, since that is not attached to
the
 type.
I think most valid existing use cases would still be supported: int execFunctionPointer(int function(int = 2) fun){ return fun(); } auto dg = (int x, int y=2){ return x+y; } writeln(range.map!dg()); int function(int=3)[] funs; funs[0](); It is up to you if it is worth the effort, of course.
Aug 26 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 6:55 PM, Timon Gehr wrote:
 On 08/27/2012 02:44 AM, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable?
Yes.
 Consider what you do with a function pointer - you pass it to someone
 else. That someone else gets it as a type, not a declaration.
If it is a template function, yes. But then you may as well pass the function pointer variable per alias, which is common.
You pass the function declaration by alias, not the function pointer declaration. In which case you will get the default arguments.
 Otherwise someone else gets nothing but a parameter declaration.

 I.e. you lose the default argument information, since that is not attached to
the
 type.
I think most valid existing use cases would still be supported: int execFunctionPointer(int function(int = 2) fun){ return fun(); } auto dg = (int x, int y=2){ return x+y; } writeln(range.map!dg()); int function(int=3)[] funs; funs[0](); It is up to you if it is worth the effort, of course.
and it falls apart immediately once you try to transfer that function pointer anywhere.
Aug 26 2012
next sibling parent reply "Chris Cain" <clcain uncg.edu> writes:
If I may, here's my suggestion on how everything could work (and 
I think should be doable) ... basically, it would be part of the 
type, but functions with the same parameters regardless of 
defaults would be implicitly convertible to one another:

------

void efpBad( int function(int) fun ) {
     return fun(); // Fails to compile, as expected
}

void efp1( int function(int) fun ) {
     return fun(1); // Compiles fine, works as expected
}

void efp1Def1( int function(int = 1) fun ) {
     return fun(); // Compiles fine, equivalent to fun(1) which 
should be expected
}


auto fn1p = (int x) { return x; };
auto fn1pDef = (int x = 2) { return x; };

auto fn2p = (int x, int y) { return x + y; };
auto fn2pDef = (int x, int y = 2) { return x + y; };



don't match.

(even though 1 has provided a "default")
efp2(fn1p); // Success ... returns 1 as expected
efp2(fn1pDef); // Ditto
efp3(fn1p); // Compiles ... implicitly convertible, returns 1 
(efp3 is providing the default above)
efp3(fn1pDef); // Successfully compiles, returns 1 ... NOT 5 
(because 1 is provided as the default in efp3 above)

// Implicitly convertible between variables
fn1p = fn1pDef; // Perfectly fine
fn1p(); // Fails to compile, must provide an argument!
fn1pDef = fn1p; // Again, fine. fn1pDef will allow the arg to 
have the default of 1 now.
fn1pDef(); // Succeeds, returns 1

fn1p = fn2pDef; // Fails to compile, as expected
fn1p = fn2p; // Nonsense, so it fails, of course

// IsExpressions
is(typeof(fn1p) == typeof(fn1pDef)); // False
is(typeof(fn1p) : typeof(fn1pDef));  // True
is(typeof(fn1pDef) : typeof(fn1p));  // True

I hope that was rigorous enough to show the behavior when passing 
around and such. I feel like this behavior would be acceptable 
for almost every use. I'm pretty sure this is how people were 
expecting it to behave before, anyway.

Is there a problem with it working this way? (Obviously, I'm no 
compiler expert)
Aug 26 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 8:49 PM, Chris Cain wrote:
 If I may, here's my suggestion on how everything could work (and I think should
 be doable) ... basically, it would be part of the type, but functions with the
 same parameters regardless of defaults would be implicitly convertible to one
 another:
What happens with the name mangling? What about overloading? template type deduction? type specialization? type equivalence? type covariance?
Aug 26 2012
parent reply "Chris Cain" <clcain uncg.edu> writes:
On Monday, 27 August 2012 at 04:01:10 UTC, Walter Bright wrote:
 What happens with the name mangling? What about overloading? 
 template type deduction? type specialization? type equivalence? 
 type covariance?
Name mangling: Without knowing enough about compiler making, I'm not sure why it matters. I couldn't answer, sorry. Overloading: I'm making a guess, but do you mean something like this? void fn( int function(int) fp ) { ... } void fn( int function(int = 1) fp ) { ... } It should fail to compile. Type specialization: Templates? Bar!( int function(int) ) would have to be different than Bar!( int function(int = 1) ), I would think. Template type deduction: I don't really understand the question with this, actually. Type equivalence, type covariance: Like the is expressions I showed before? Or what do you mean? They should be implicitly convertible between each other. I'm just giving you the standpoint of a user of the language, not necessarily a compiler writer (I have an extremely basic understanding of compilers ... mostly, I just know lexing and parsing at the moment), so I'm having difficulty understanding what some of the problems with this is.
Aug 26 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 9:25 PM, Chris Cain wrote:
 On Monday, 27 August 2012 at 04:01:10 UTC, Walter Bright wrote:
 What happens with the name mangling? What about overloading? template type
 deduction? type specialization? type equivalence? type covariance?
Name mangling: Without knowing enough about compiler making, I'm not sure why it matters. I couldn't answer, sorry.
The mangled names have a 1:1 correspondence with types. A mangled name can, for example, be reversed into a type. If default args form part of the type, then they'll have to be mangled in, too. This causes a rather long list of substantial problems.
Aug 26 2012
next sibling parent reply Piotr Duda <duda.piotr gmail.com> writes:
2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/26/2012 9:25 PM, Chris Cain wrote:
 On Monday, 27 August 2012 at 04:01:10 UTC, Walter Bright wrote:
 What happens with the name mangling? What about overloading? template
 type
 deduction? type specialization? type equivalence? type covariance?
Name mangling: Without knowing enough about compiler making, I'm not sur=
e
 why it
 matters. I couldn't answer, sorry.
The mangled names have a 1:1 correspondence with types. A mangled name ca=
n,
 for example, be reversed into a type.

 If default args form part of the type, then they'll have to be mangled in=
,
 too. This causes a rather long list of substantial problems.
Default args should be part of types (for passing them as template args etc, implicity convertable if they differs only on defaults) but not mangled in (since mangling is revelant only for linking, where defaults doesn't matter). --=20 =E9=97=87=E3=81=AB=E9=9A=A0=E3=82=8C=E3=81=9F=E9=BB=92=E3=81=84=E5=8A=9B =E5=BC=B1=E3=81=84=E5=BF=83=E3=82=92=E6=93=8D=E3=82=8B
Aug 26 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 11:14 PM, Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
And then there's a list of other bugs that show up. Now you have two different types showing up as the same type (i.e. name) to the linker, and you've got weird collisions. Really, a type that is the same only different is a recipe for endless subtle and ugly problems. It's a nightmare.
Aug 27 2012
parent reply Piotr Duda <duda.piotr gmail.com> writes:
2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/26/2012 11:14 PM, Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
And then there's a list of other bugs that show up. Now you have two different types showing up as the same type (i.e. name) to the linker, an=
d
 you've got weird collisions.
For linker these types should be identical, so there shouldn't be any collisions, unless D handles default args fundamentally different than C++. --=20 =E9=97=87=E3=81=AB=E9=9A=A0=E3=82=8C=E3=81=9F=E9=BB=92=E3=81=84=E5=8A=9B =E5=BC=B1=E3=81=84=E5=BF=83=E3=82=92=E6=93=8D=E3=82=8B
Aug 27 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 10:48 AM, Piotr Duda wrote:
 2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/26/2012 11:14 PM, Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
And then there's a list of other bugs that show up. Now you have two different types showing up as the same type (i.e. name) to the linker, and you've got weird collisions.
For linker these types should be identical, so there shouldn't be any collisions, unless D handles default args fundamentally different than C++.
You said they should be part of the type for passing as template args: auto foo(T)(T dg){ return dg(); } // therefore assert(foo((int x=2)=>x)==2); // this instantiation assert(foo((int x=3)=>x)==3); // must differ from this one // ergo, they cannot have the same mangled symbol name!
Aug 27 2012
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 08/27/12 12:54, Timon Gehr wrote:
 On 08/27/2012 10:48 AM, Piotr Duda wrote:
 2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/26/2012 11:14 PM, Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
And then there's a list of other bugs that show up. Now you have two different types showing up as the same type (i.e. name) to the linker, and you've got weird collisions.
For linker these types should be identical, so there shouldn't be any collisions, unless D handles default args fundamentally different than C++.
You said they should be part of the type for passing as template args: auto foo(T)(T dg){ return dg(); } // therefore assert(foo((int x=2)=>x)==2); // this instantiation assert(foo((int x=3)=>x)==3); // must differ from this one // ergo, they cannot have the same mangled symbol name!
Anonymous functions must be unique anyway. artur
Aug 27 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 01:16 PM, Artur Skawina wrote:
 On 08/27/12 12:54, Timon Gehr wrote:
 On 08/27/2012 10:48 AM, Piotr Duda wrote:
 2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/26/2012 11:14 PM, Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
And then there's a list of other bugs that show up. Now you have two different types showing up as the same type (i.e. name) to the linker, and you've got weird collisions.
For linker these types should be identical, so there shouldn't be any collisions, unless D handles default args fundamentally different than C++.
You said they should be part of the type for passing as template args: auto foo(T)(T dg){ return dg(); } // therefore assert(foo((int x=2)=>x)==2); // this instantiation assert(foo((int x=3)=>x)==3); // must differ from this one // ergo, they cannot have the same mangled symbol name!
Anonymous functions must be unique anyway. artur
What is the point?
Aug 27 2012
next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 4:46 AM, Timon Gehr wrote:
 What is the point?
For that case, there is none, as you should be passing the lambda by alias rather than by pointer.
Aug 27 2012
prev sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 08/27/12 13:46, Timon Gehr wrote:
 On 08/27/2012 01:16 PM, Artur Skawina wrote:
 On 08/27/12 12:54, Timon Gehr wrote:
 On 08/27/2012 10:48 AM, Piotr Duda wrote:
 2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/26/2012 11:14 PM, Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
And then there's a list of other bugs that show up. Now you have two different types showing up as the same type (i.e. name) to the linker, and you've got weird collisions.
For linker these types should be identical, so there shouldn't be any collisions, unless D handles default args fundamentally different than C++.
You said they should be part of the type for passing as template args: auto foo(T)(T dg){ return dg(); } // therefore assert(foo((int x=2)=>x)==2); // this instantiation assert(foo((int x=3)=>x)==3); // must differ from this one // ergo, they cannot have the same mangled symbol name!
Anonymous functions must be unique anyway.
What is the point?
I misunderstood, my fault, sorry. A template instantiation with a parameter containing a default argument must of course reflect that argument in the mangled name. Are there other cases where def-args can't be ignored? (not where they are just "lost", that is not a problem) artur
Aug 27 2012
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 07:52, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/26/2012 9:25 PM, Chris Cain wrote:

 On Monday, 27 August 2012 at 04:01:10 UTC, Walter Bright wrote:

 What happens with the name mangling? What about overloading? template
 type
 deduction? type specialization? type equivalence? type covariance?
Name mangling: Without knowing enough about compiler making, I'm not sure why it matters. I couldn't answer, sorry.
The mangled names have a 1:1 correspondence with types. A mangled name can, for example, be reversed into a type. If default args form part of the type, then they'll have to be mangled in, too. This causes a rather long list of substantial problems.
This sounds like an implementation detail/dmd quirk is defining the language spec... I generally agree with the usage proposal above, it should be metadata that doesn't affect type equivalence (although I'd probably expect is(f1 == f2def) should be true rather than false). If that info needs to be mangled into the name to reproduce the type later, fine, is that a problem? Perhaps stick it at the end of the mangled name in a new metadata suffix that is truncated prior to any comparisons for equality? This actually rather relates to the attribute/annotation proposals/conversations we were having some time back. If this is addressed, then it may enable attributes too.
Aug 27 2012
parent reply "Paulo Pinto" <pjmlp progtools.org> writes:
On Monday, 27 August 2012 at 07:54:12 UTC, Manu wrote:
 On 27 August 2012 07:52, Walter Bright 
 <newshound2 digitalmars.com> wrote:

 On 8/26/2012 9:25 PM, Chris Cain wrote:

 On Monday, 27 August 2012 at 04:01:10 UTC, Walter Bright 
 wrote:

 What happens with the name mangling? What about overloading? 
 template
 type
 deduction? type specialization? type equivalence? type 
 covariance?
Name mangling: Without knowing enough about compiler making, I'm not sure why it matters. I couldn't answer, sorry.
The mangled names have a 1:1 correspondence with types. A mangled name can, for example, be reversed into a type. If default args form part of the type, then they'll have to be mangled in, too. This causes a rather long list of substantial problems.
This sounds like an implementation detail/dmd quirk is defining the language spec...
For it sounds like constraining the language while keeping the C/C++ linker semantics, instead of using a D aware linker. Not trolling, just trying to understand the design constraints. -- Paulo
Aug 27 2012
next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 11:01, Paulo Pinto <pjmlp progtools.org> wrote:

 On Monday, 27 August 2012 at 07:54:12 UTC, Manu wrote:

 This sounds like an implementation detail/dmd quirk is defining the
 language spec...
For it sounds like constraining the language while keeping the C/C++ linker semantics, instead of using a D aware linker.
I don't see how the linker enters into it. Default args are irrelevant to the linker.
Aug 27 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 1:10 AM, Manu wrote:
 I don't see how the linker enters into it. Default args are irrelevant to the
 linker.
Consider name mangling and what it's for. Now consider two *different* types mangling to the same name. D fundamentally depends on a 1:1 correspondence between types and name mangling, not 1:n or n:1.
Aug 27 2012
parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 08/27/12 10:24, Walter Bright wrote:
 On 8/27/2012 1:10 AM, Manu wrote:
 I don't see how the linker enters into it. Default args are irrelevant to the
 linker.
Consider name mangling and what it's for. Now consider two *different* types mangling to the same name. D fundamentally depends on a 1:1 correspondence between types and name mangling, not 1:n or n:1.
D or DMD? Because the relevant bug /was/ an implementation problem. I can see how changing the implementation is not really practical short-term, hence didn't comment on it at the time, but even the examples given as "unsound" were fine and could be well defined. Type equivalence != type identity. Default args shouldn't be part of the mangled names. But having them (defargs) for function pointers can be very useful, esp. for generics ("void f(int=42); auto fp = &f; fp();" could be made to work), it reduces the amount of glue required and increases productivity - one of D's main strengths. In the mean time hacks such as this one can probably help sometimes, like in the automatically-generated-bindings cases: void f(int one, string two, double three) { import std.stdio; writeln(one, two, three); } void main() { auto fp = CWDA!(typeof(&f), "1", "\"two\"", "3.14")(&f); fp(1, "two", 3.14); fp(1, "two"); fp(1); fp(); fp(four/4); } int four = 4; static struct _CWDA(C, DA...) { C ptr; alias ptr this; import std.traits; static if (DA.length==3) // POC; handling other cases left as an exercise ;) auto ref opCall(ParameterTypeTuple!C[0..$-3] a, ParameterTypeTuple!C[$-3] da1 = mixin(DA[$-3]), ParameterTypeTuple!C[$-2] da2 = mixin(DA[$-2]), ParameterTypeTuple!C[$-1] da3 = mixin(DA[$-1])) { return ptr(a, da1, da2, da3); } } auto CWDA(C, DA...)(C c) { _CWDA!(C, DA) p; p.ptr = c; return p; } but that's not really an acceptable solution, obviously. artur
Aug 27 2012
parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 3:34 AM, Artur Skawina wrote:
 On 08/27/12 10:24, Walter Bright wrote:
 On 8/27/2012 1:10 AM, Manu wrote:
 I don't see how the linker enters into it. Default args are irrelevant to the
 linker.
Consider name mangling and what it's for. Now consider two *different* types mangling to the same name. D fundamentally depends on a 1:1 correspondence between types and name mangling, not 1:n or n:1.
D or DMD?
D.
 Because the relevant bug /was/ an implementation problem.
Which revealed a design problem in the language.
 but that's not really an acceptable solution, obviously.
Right, and having a bunch of half-assed solutions is not a good long term solution. We all know that C++ is full of such, and they've caused endless grief as subsequent designers try to fix them, and pages and pages of impenetrable explanations in the C++ spec.
Aug 27 2012
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 1:01 AM, Paulo Pinto wrote:
 For it sounds like constraining the language while keeping the C/C++ linker
 semantics, instead of using a D aware linker.
That's just one of the problems. And no, we can't go write our own linker on all platforms.
Aug 27 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, August 27, 2012 08:14:41 Piotr Duda wrote:
 Default args should be part of types (for passing them as template
 args etc, implicity convertable if they differs only on defaults) but
 not mangled in (since mangling is revelant only for linking, where
 defaults doesn't matter).
Default args are essentially copy-pasted at the call point. It makes no sense for them to be part of the type, and it makes no sense to use them with function pointers or function literals. - Jonathan M Davis
Aug 27 2012
parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 08:26:15 UTC, Jonathan M Davis wrote:
 On Monday, August 27, 2012 08:14:41 Piotr Duda wrote:
 Default args should be part of types (for passing them as 
 template
 args etc, implicity convertable if they differs only on 
 defaults) but
 not mangled in (since mangling is revelant only for linking, 
 where
 defaults doesn't matter).
Default args are essentially copy-pasted at the call point. It makes no sense for them to be part of the type, and it makes no sense to use them with function pointers or function literals.
Agreed. So see my way of simulating these at run-time without adverse type consequences or any overhead for existing functions.
Aug 27 2012
prev sibling next sibling parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
 The mangled names have a 1:1 correspondence with types. A 
 mangled name can, for example, be reversed into a type.

 If default args form part of the type, then they'll have to be 
 mangled in, too. This causes a rather long list of substantial 
 problems.
The following has other pervasive ramifications, to say the least. Worth thinking about IMHO nevertheless, even if to eventually say "no". So here's a completely different way that the effect of default arguments can be obtained without messing with the type system. Suppose a function pointer is always permitted be called with fewer actual arguments than parameters declared. I'm speculating uncritically here, so bear with me; this could be restricted to calling function pointers & delegate pointers, or with an avalanche of consequences permitted also for direct function calling where it will interact with the overloading rules, or ... there are many possible arrangements. So let's forge ahead for now with the basic idea for function pointers. Suppose a function pointer can be called with fewer actual arguments than the number of parameters in its declaration. Suppose that when such a call is made, the missing arguments will always be assigned the default initialization for their types (default default-arguments!). Now suppose that a language mechanism is provided so that code in the function body can determine how many actual arguments were supplied at the point of call. Now any function pointer can simulate other default arguments (non-default default arguments) by testing the actual number of arguments supplied and assigning defaults overtly to the remainder inside the function body. No need for new types: this is a run-time action. Now you can stamp on this with big logic boots, but let's see if I can escape from some of the trampling!
Aug 27 2012
parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
 Suppose a function pointer can be called with fewer actual 
 arguments than the number of parameters in its declaration. 
 Suppose that when such a call is made, the missing arguments 
 will always be assigned the default initialization for their 
 types (default default-arguments!). Now suppose that a language 
 mechanism is provided so that code in the function body can 
 determine how many actual arguments were supplied at the point 
 of call.

 Now any function pointer can simulate other default arguments 
 (non-default default arguments) by testing the actual number of 
 arguments supplied and assigning defaults overtly to the 
 remainder inside the function body. No need for new types: this 
 is a run-time action.
That's a great idea Carl! You mean something like this: int sum(int a, int b) { if( argc == 1 ) b = 1; //default for b if not supplied return a + b; } //... auto f = &sum; //... auto x = sum(y); //function pointer call, so fewer args permitted
Aug 27 2012
next sibling parent reply "Simen Kjaeraas" <simen.kjaras gmail.com> writes:
On Mon, 27 Aug 2012 11:57:45 +0200, Carl Sturtivant <sturtivant gmail.com>  
wrote:

 Suppose a function pointer can be called with fewer actual arguments  
 than the number of parameters in its declaration. Suppose that when  
 such a call is made, the missing arguments will always be assigned the  
 default initialization for their types (default default-arguments!).  
 Now suppose that a language mechanism is provided so that code in the  
 function body can determine how many actual arguments were supplied at  
 the point of call.

 Now any function pointer can simulate other default arguments  
 (non-default default arguments) by testing the actual number of  
 arguments supplied and assigning defaults overtly to the remainder  
 inside the function body. No need for new types: this is a run-time  
 action.
That's a great idea Carl!
You *do* know you're talking to yourself, right? -- Simen
Aug 27 2012
parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 10:12:28 UTC, Simen Kjaeraas wrote:
 On Mon, 27 Aug 2012 11:57:45 +0200, Carl Sturtivant 
 <sturtivant gmail.com> wrote:

 Suppose a function pointer can be called with fewer actual 
 arguments than the number of parameters in its declaration. 
 Suppose that when such a call is made, the missing arguments 
 will always be assigned the default initialization for their 
 types (default default-arguments!). Now suppose that a 
 language mechanism is provided so that code in the function 
 body can determine how many actual arguments were supplied at 
 the point of call.

 Now any function pointer can simulate other default arguments 
 (non-default default arguments) by testing the actual number 
 of arguments supplied and assigning defaults overtly to the 
 remainder inside the function body. No need for new types: 
 this is a run-time action.
That's a great idea Carl!
You *do* know you're talking to yourself, right?
My alter ego, you mean? Someone has to talk to the poor guy.
Aug 27 2012
prev sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
 That's a great idea Carl! You mean something like this:

 int sum(int a, int b) {
   if( argc == 1 ) b = 1; //default for b if not supplied

   return a + b;
 }

 //...

 auto f = &sum;

 //...

 auto x = sum(y);  //function pointer call, so fewer args 
 permitted
oops! last line was supposed to be: auto x = f(y); //function pointer call, so fewer args permitted And for the record, 'argc' in the above is a name that has a similar status to 'this' and is equal to the number of arguments actually passed. Probably there's a better choice of name.
Aug 27 2012
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 11:12, Jonathan M Davis <jmdavisProg gmx.com> wrote:

 and it makes no sense to use them with function pointers or function
 literals.
If that were true, we wouldn't be having this discussion.
Aug 27 2012
parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 08:39:01 UTC, Manu wrote:
 On 27 August 2012 11:12, Jonathan M Davis <jmdavisProg gmx.com> 
 wrote:

 and it makes no sense to use them with function pointers or 
 function
 literals.
If that were true, we wouldn't be having this discussion.
I think that we (at a minimum me and my alter ego) would find it helpful to acknowledge something along the following lines (in no particular order). 1. There is call for a default argument mechanism when calling function or delegate pointers. [Those who've used these extensively know they want them! Now we think about it, we want them too.] 2. The existing default argument mechanism which is to simply pad the call at compile time with the defaults read from the function definition is ill suited to function pointer calls. [Those who want sane compiler machinery know this.] 3. It's helpful to overtly acknowledge that function calling and function pointer calling are quite different. [They are already different in D because overloading is applied statically, just as default arguments have now become something only applied statically. A function pointer is to one function and overloading therefore doesn't exist.] 4. Function pointer calls being dynamical could perhaps have a dynamical mechanism to assign default arguments at run time, so as to go along with notions 1,2,3 above. [That way the function decides which arguments have been defaulted and assigns their default values at runtime, so the language merely needs a mechanism for the function body when run to find out which parameters it needs to assign defaults to i.e. how many arguments have actually been provided when the function pointer was called.] ---hence my earlier proposal. I'm feeling lonely ---please at least shoot it down!
Aug 27 2012
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 05:05 AM, Walter Bright wrote:
 On 8/26/2012 6:55 PM, Timon Gehr wrote:
 On 08/27/2012 02:44 AM, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable?
Yes.
 Consider what you do with a function pointer - you pass it to someone
 else. That someone else gets it as a type, not a declaration.
If it is a template function, yes. But then you may as well pass the function pointer variable per alias, which is common.
You pass the function declaration by alias, not the function pointer declaration. In which case you will get the default arguments.
The function declaration is not necessarily available and the pointer/delegate might not be a constant. This is the point of function pointers and delegates.
 Otherwise someone else gets nothing but a parameter declaration.

 I.e. you lose the default argument information, since that is not
 attached to the
 type.
I think most valid existing use cases would still be supported: int execFunctionPointer(int function(int = 2) fun){ return fun(); } auto dg = (int x, int y=2){ return x+y; } writeln(range.map!dg()); int function(int=3)[] funs; funs[0](); It is up to you if it is worth the effort, of course.
and it falls apart immediately once you try to transfer that function pointer anywhere.
No it does not. The examples people have posted where it breaks their code that I am aware of all would work with this scheme. What possible use case do you think would have to be supported by function pointer/delegate default arguments that cannot be supported without serious implementation problems?
Aug 27 2012
prev sibling next sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 00:44:54 UTC, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args 
 would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
This problem goes away if the defaults are always the same: the .init defaults, and if function pointers (but not functions) can be called with fewer arguments than there are parameters in their definition. When the call is made, the remaining argument can be provided using the standard .init defaults. And because the standard .init defaults can be manipulated via defining new types for the parameters, it becomes the parameter's types that the defaults are found from, and defaults do not need to be encoded in the types of function pointers. And if you don't like all function pointers getting this treatment, then it could be extended only to those that are given a qualification indicating so: one more binary bit added to mangling of its type. This mechanism could be made to work equally well with extern(C) etc. functions.
Aug 27 2012
prev sibling parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 00:44:54 UTC, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args 
 would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
I think this is the right behavior too. Default arguments are IMHO just a compact way to write some simply related overloaded functions, e.g. thus: int sum(int x, int y = 1 ) { return x + y; } is just a compact way to write int sum(int x, int y) { return x + y; } int sum(int x) { return sum(x, 1); } and a function pointer refers to a single function, so overloading and default arguments are irrelevant to function pointers. It just so happens that the particularly simple overloading abbreviated by a function definition with default arguments is easily optimized by the compiler with only one function arriving at the linker: the most general one. And that's the one whose address is supplied if the unary & operator is applied to it. So that's the problem with 'int sum(int x, int y = 1 ) ...' --- it's not one function under the interpretation above. So how could we interpret int function(int x, int y = 1) sum; in the above interpretation? What's happening right now is that sum is considered to be a single function pointer, so we discard the default to get the most general function type much like the effect of unary & above. So the trouble with the supposed type 'int function(int x, int y = 1)' is that it is not one type, just as the function sum above is not one function. [Speculation mode starts here.] So what if function pointer declarations with defaults are considered to define several function pointers and not just one. e.g. thus: int function(int x, int y = 1) sum; could be taken by the compiler to mean int function(int x, int y) sum; //all the rest call back the general function pointer int function(int x) sum = function sum(int x) { return sum(x,1); } I haven't explored all of the ramifications of this, but it looks as if it solves quite a few problems without changing the type system, or affecting existing code. And there's a simple model of what's going on: overloading, just generalized slightly. This may not be clean enough or general enough. But the basic point that something like 'int function(int x, int y = 1)' is a sequence of types is worth attention. A declaration using a sequence of types should produce a sequence of function pointers under this interpretation. Exactly how "sequence of types" and "sequence of pointers" should be wrapped up for best effect is then the language design decision to make. There are several other interesting ways to go, some involving getting more general and allowing other kinds of overloading with function pointer sequences, not just the special kind that comes from default arguments, but I'll stop here for now.
Aug 28 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/28/2012 10:33 PM, Carl Sturtivant wrote:
 On Monday, 27 August 2012 at 00:44:54 UTC, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
I think this is the right behavior too. Default arguments are IMHO just a compact way to write some simply related overloaded functions, e.g. thus: int sum(int x, int y = 1 ) { return x + y; } is just a compact way to write int sum(int x, int y) { return x + y; } int sum(int x) { return sum(x, 1); } ...
This interpretation is simply wrong. import std.stdio, std.c.stdlib; void* alloca20bytes(void* x = alloca(20)){ return x; } // your suggested transformation: /+void* alloca20bytes(void* x){ return x; } void* alloca20bytes(){ return alloca20bytes(alloca(20)); }+/ // would break the caller: void main(){ auto x = (cast(int*)alloca20bytes())[0..5]; x[] = 0; x[] += 2; writeln(x); }
Aug 28 2012
parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
On Tuesday, 28 August 2012 at 21:40:01 UTC, Timon Gehr wrote:
 On 08/28/2012 10:33 PM, Carl Sturtivant wrote:
 On Monday, 27 August 2012 at 00:44:54 UTC, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args 
 would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
I think this is the right behavior too. Default arguments are IMHO just a compact way to write some simply related overloaded functions, e.g. thus: int sum(int x, int y = 1 ) { return x + y; } is just a compact way to write int sum(int x, int y) { return x + y; } int sum(int x) { return sum(x, 1); } ...
This interpretation is simply wrong. import std.stdio, std.c.stdlib; void* alloca20bytes(void* x = alloca(20)){ return x; } // your suggested transformation: /+void* alloca20bytes(void* x){ return x; } void* alloca20bytes(){ return alloca20bytes(alloca(20)); }+/ // would break the caller: void main(){ auto x = (cast(int*)alloca20bytes())[0..5]; x[] = 0; x[] += 2; writeln(x); }
Function inlining or not in the presence of alloca calls and similar using the existing stack frame are problematic. If the first call was inlined by the compiler, that would un-break the "problem". I suggest that we simply define default arguments via the transformation I suggested, and regard void* alloca20bytes(void* x = alloca(20)){ return x; } as broken. It's not compelling for a lot of reasons. Of course there may be something else wrong with the transformation! Fire away.
Aug 28 2012
parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Tuesday, 28 August 2012 at 23:34:54 UTC, Carl Sturtivant wrote:
 On Tuesday, 28 August 2012 at 21:40:01 UTC, Timon Gehr wrote:
 On 08/28/2012 10:33 PM, Carl Sturtivant wrote:
 On Monday, 27 August 2012 at 00:44:54 UTC, Walter Bright 
 wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default 
 args would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
I think this is the right behavior too. Default arguments are IMHO just a compact way to write some simply related overloaded functions, e.g. thus: int sum(int x, int y = 1 ) { return x + y; } is just a compact way to write int sum(int x, int y) { return x + y; } int sum(int x) { return sum(x, 1); } ...
This interpretation is simply wrong. import std.stdio, std.c.stdlib; void* alloca20bytes(void* x = alloca(20)){ return x; } // your suggested transformation: /+void* alloca20bytes(void* x){ return x; } void* alloca20bytes(){ return alloca20bytes(alloca(20)); }+/ // would break the caller: void main(){ auto x = (cast(int*)alloca20bytes())[0..5]; x[] = 0; x[] += 2; writeln(x); }
Function inlining or not in the presence of alloca calls and similar using the existing stack frame are problematic. If the first call was inlined by the compiler, that would un-break the "problem". I suggest that we simply define default arguments via the transformation I suggested, and regard void* alloca20bytes(void* x = alloca(20)){ return x; } as broken. It's not compelling for a lot of reasons. Of course there may be something else wrong with the transformation! Fire away.
Another, more conservative, approach would be for the compiler to regard the non-general functions in such a group created by my transformation to be always inlined, so the default parameters are always evaluated before the call. Then your interesting example would work the way you expect.
Aug 29 2012
prev sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Tuesday, 28 August 2012 at 20:33:50 UTC, Carl Sturtivant wrote:
 On Monday, 27 August 2012 at 00:44:54 UTC, Walter Bright wrote:
 On 8/26/2012 4:50 PM, Timon Gehr wrote:
 On 08/27/2012 12:41 AM, Walter Bright wrote:
 The trouble for function pointers, is that any default args 
 would need
 to be part of the type, not the declaration.
They could be made part of the variable declaration.
You mean part of the function pointer variable? Consider what you do with a function pointer - you pass it to someone else. That someone else gets it as a type, not a declaration. I.e. you lose the default argument information, since that is not attached to the type.
I think this is the right behavior too. Default arguments are IMHO just a compact way to write some simply related overloaded functions, e.g. thus: int sum(int x, int y = 1 ) { return x + y; } is just a compact way to write int sum(int x, int y) { return x + y; } int sum(int x) { return sum(x, 1); } and a function pointer refers to a single function, so overloading and default arguments are irrelevant to function pointers. It just so happens that the particularly simple overloading abbreviated by a function definition with default arguments is easily optimized by the compiler with only one function arriving at the linker: the most general one. And that's the one whose address is supplied if the unary & operator is applied to it. So that's the problem with 'int sum(int x, int y = 1 ) ...' --- it's not one function under the interpretation above. So how could we interpret int function(int x, int y = 1) sum; in the above interpretation? What's happening right now is that sum is considered to be a single function pointer, so we discard the default to get the most general function type much like the effect of unary & above. So the trouble with the supposed type 'int function(int x, int y = 1)' is that it is not one type, just as the function sum above is not one function. [Speculation mode starts here.] So what if function pointer declarations with defaults are considered to define several function pointers and not just one. e.g. thus: int function(int x, int y = 1) sum; could be taken by the compiler to mean int function(int x, int y) sum; //all the rest call back the general function pointer int function(int x) sum = function sum(int x) { return sum(x,1); } I haven't explored all of the ramifications of this, but it looks as if it solves quite a few problems without changing the type system, or affecting existing code. And there's a simple model of what's going on: overloading, just generalized slightly. This may not be clean enough or general enough. But the basic point that something like 'int function(int x, int y = 1)' is a sequence of types is worth attention. A declaration using a sequence of types should produce a sequence of function pointers under this interpretation. Exactly how "sequence of types" and "sequence of pointers" should be wrapped up for best effect is then the language design decision to make. There are several other interesting ways to go, some involving getting more general and allowing other kinds of overloading with function pointer sequences, not just the special kind that comes from default arguments, but I'll stop here for now.
Here is an additional implementation possibility for the minimal version where there are no new types. Parameters, e.g.: void fun(int x, int function(int x, int y = 1) fun) { //... j = fun(3); //alerts the compiler //... } could be analogously compiled to behave similarly, because the default arguments are 'nearby'. Here's what the compiler could in effect produce from the above. int fun(int x, int function(int x, int y) fun) { int function(int x) fun = function void(int x) { return fun(x, 1); } //... j = fun(3); //... } i.e. use local variables for the required specialized versions of the function. Of course then the question of "how near is nearby?" needs a design decision.
Aug 29 2012
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 01:41, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/26/2012 3:26 PM, Manu wrote:

 I just updated to 2.60 and found errors throughout my code where function
 pointers default args no longer work.
 _Every single project_ I've written in D, 5 projects, don't work anymore,

 including my projects at work.

 I found this discussion: http://d.puremagic.com/issues/**
 show_bug.cgi?id=3866 <http://d.puremagic.com/issues/show_bug.cgi?id=3866>
 It seems the change was just decided and implemented with basically no
 discussion or argument at all :/

 My use cases are dynamic linkage, and cross-language integration.
 I can't manually interact with DLL's containing API's that expect to have
 default arguments if function pointers no longer support them.
 Also when receiving foreign language function pointers, they frequently
 need to
 have default args too.

 I also integrate with many C style API's (rendering engines and the
 like), which
 involve registration of various callbacks, and lots of those have default
 args too.

 I find this particularly surprising, since I recently motivated
 implementation
 of new traits which could parse default args from parameter lists, and
 use that
 to generation function pointers in templates which auto-magically clone
 functions parameter lists verbatim, specifically including the default
 args...
The trouble is that, as 3866 shows, there is no design anyone could come up with that worked in a consistent manner. The only consistent way out was to make default arguments a characteristic of the declaration, not of the type.
To be fair, it's not a very lively discussion. Spans 1 day, and only 3 people comment :) Is that really an exhaustive set of options? It seems the other possibility (including the default arg in the equivalence test) wasn't really considered. The part that I don't understand is how the situation manifests in the first place: auto foo = (int a = 1) { return a;}; auto bar = (int a) { return a;}; writeln(foo()); // writes '1' writeln(bar()); // writes '1' also! These are 2 distinct assignments. I would expect foo and bar *would* each take a type that is technically equivalent, but foo's type would have the bonus default arg in its type record, just as a function pointer that were declared explicitly. At what point does the compiler become confused? They never interact... The expected behaviour seems obvious to me, foo and bar should effectively be: int function(int a = 1) foo = ..; int function(int a) bar = ...; I presume if I typed that code, there would be no problem? The discussion is obviously regarding implementation details...? The trouble for function pointers, is that any default args would need to
 be part of the type, not the declaration.
I thought they were part of the type already? I would have thought that's where they should be. Why is their new home more 'proper'? I know it broke code (for many others, too), and I'm very sorry about that,
 but I don't see another way out.
It's a rather major breakage. I've seen you reject far more important changes solely on the grounds that they are a breaking change before... (Many uses of default arguments can be replaced with overloaded functions.)

Can you suggest how? I can't think of a feasible approach.

You can't overload function pointers (multiple instances of variables with
the same name). And by definition of a function *pointer*, I don't own the
target function to overload it.
I can't reasonably produce static wrappers either, unless the wrapper
receives the function pointer I intend to call as an arg, which is super
nasty.

I can probably address the global/static ones via wrappers, but I don't
love redundant function calls, it reduces debug build performance (see: all
my rants about __forceinline), but that would only address the problem in a
few situations.
This also undoes all that good work recently with the parameter list traits
_<
As I see it, calling a function is possibly the single most important thing a programming language does. Flexibility in this regard is valuable. Default args in function pointers was a HUGE selling point of D to me, I've made extremely liberal use of this feature to great benefit throughout all my projects. But perhaps most importantly, this change has a huge impact on my code at work... I don't really want to imagine telling my boss that I need to rewrite the engine bindings and integration code. I'll look like the biggest dick this side of the Baltic :/
Aug 26 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/26/2012 5:06 PM, Manu wrote:
 To be fair, it's not a very lively discussion. Spans 1 day, and only 3 people
 comment :)
If you want a lively discussion, start one up about what to name a keyword :-)
 Is that really an exhaustive set of options? It seems the other possibility
 (including the default arg in the equivalence test) wasn't really considered.

 The part that I don't understand is how the situation manifests in the first
place:

      auto foo = (int a = 1) { return a;};
      auto bar = (int a) { return a;};

      writeln(foo()); // writes '1'
      writeln(bar()); // writes '1' also!

 These are 2 distinct assignments. I would expect foo and bar /would/ each take
a
 type that is technically equivalent, but foo's type would have the bonus
default
 arg in its type record, just as a function pointer that were declared
explicitly.
That's the way it used to work. The problem is, what are those types? Are they the same types or not? You're saying sometimes they are the same type, sometimes they are not. It's a mess.
 At what point does the compiler become confused? They never interact... The
 expected behaviour seems obvious to me, foo and bar should effectively be:
     int function(int a = 1) foo = ..;
     int function(int a) bar = ...;
It seems obvious only in the trivial case. What if you passed foo to a function? Now what type is it? What is the mangling for the type?
 I presume if I typed that code, there would be no problem?

 The discussion is obviously regarding implementation details...?
No.
     The trouble for function pointers, is that any default args would need to
be
     part of the type, not the declaration.


 I thought they were part of the type already? I would have thought that's where
 they should be. Why is their new home more 'proper'?
Because are the types the same or not?
     I know it broke code (for many others, too), and I'm very sorry about that,
     but I don't see another way out.


 It's a rather major breakage. I've seen you reject far more important changes
 solely on the grounds that they are a breaking change before...
Unfortunately, it was already broken, as the bug report showed. It's a matter of where the crack shows up.
     (Many uses of default arguments can be replaced with overloaded functions.)


 Can you suggest how? I can't think of a feasible approach.
I'd have to see what you were trying to do.
 You can't overload function pointers (multiple instances of variables with the
 same name). And by definition of a function /pointer/, I don't own the target
 function to overload it.
 I can't reasonably produce static wrappers either, unless the wrapper receives
 the function pointer I intend to call as an arg, which is super nasty.

 I can probably address the global/static ones via wrappers, but I don't love
 redundant function calls, it reduces debug build performance (see: all my rants
 about __forceinline), but that would only address the problem in a few
situations.
 This also undoes all that good work recently with the parameter list traits >_<


 As I see it, calling a function is possibly the single most important thing a
 programming language does. Flexibility in this regard is valuable. Default args
 in function pointers was a HUGE selling point of D to me, I've made extremely
 liberal use of this feature to great benefit throughout all my projects.
 But perhaps most importantly, this change has a huge impact on my code at
 work... I don't really want to imagine telling my boss that I need to rewrite
 the engine bindings and integration code. I'll look like the biggest dick this
 side of the Baltic :/
If you can show me a generic example of what you are trying to do, perhaps we can suggest a reasonable alternative.
Aug 26 2012
parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 03:51, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/26/2012 5:06 PM, Manu wrote:

 type that is technically equivalent, but foo's type would have the bonus
 default
arg in its type record, just as a function pointer that were declared
 explicitly.
That's the way it used to work. The problem is, what are those types? Are they the same types or not? You're saying sometimes they are the same type, sometimes they are not. It's a mess.
They are always equivalent. The default args are more like metadata. At what point does the compiler become confused? They never interact... The
 expected behaviour seems obvious to me, foo and bar should effectively be:
     int function(int a = 1) foo = ..;
     int function(int a) bar = ...;
It seems obvious only in the trivial case. What if you passed foo to a function? Now what type is it? What is the mangling for the type?
Well the function you pass foo to will define the type is receives. If it receives by template, then it'll receive the default args, if it defines the function pointer type it receives explicitly (or uses another compatible definition), the default args may be lost (or different) in the new instance across the function call. I don't see why it needs to be mangled in. The linker doesn't care. The trouble for function pointers, is that any default args would need
 to be
     part of the type, not the declaration.


 I thought they were part of the type already? I would have thought that's
 where
 they should be. Why is their new home more 'proper'?
Because are the types the same or not?
They're equivalent, but not identical; one has some metadata the other doesn't have. I know it broke code (for many others, too), and I'm very sorry about
 that,
     but I don't see another way out.


 It's a rather major breakage. I've seen you reject far more important
 changes
 solely on the grounds that they are a breaking change before...
Unfortunately, it was already broken, as the bug report showed. It's a matter of where the crack shows up.
Does the bug report actually demonstrate how it was causing anybody any problems? It seemed a rather contrived scenario that just illustrated that there was a bug. Also, I think it could be fixed so the scenario in the bug report worked as expected (I still don't understand why it did't work in the first place).
Aug 27 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 1:08 AM, Manu wrote:
 Does the bug report actually demonstrate how it was causing anybody any
 problems? It seemed a rather contrived scenario that just illustrated that
there
 was a bug.
It was probably reduced from a larger scenario. Reduced bugs usually look pretty twisted.
 Also, I think it could be fixed so the scenario in the bug report worked as
 expected (I still don't understand why it did't work in the first place).
Because the two types were considered to be the same, only different. ---------------------------------- Please post a canonical example of how you use this, so we can think of an alternative.
Aug 27 2012
next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 11:28, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/27/2012 1:08 AM, Manu wrote:

  Also, I think it could be fixed so the scenario in the bug report worked
 as
 expected (I still don't understand why it did't work in the first place).
Because the two types were considered to be the same, only different.
And how was that a problem? They never interacted in the example, the assignments were totally separate, they shouldn't have been confused. Just speculating, but it just looks like the type was misrepresented when it was looked up from a map by name or something, and matched the wrong cached definition... or something along those lines. It looks like a bug exposed from implementation detail, I can't see anything in the bug report that shouldn't theoretically work fine. ------------------------------**----
 Please post a canonical example of how you use this, so we can think of an
 alternative.
Well likewise, can you provide an example where, assuming that one bug were fixed, that the old approach actually caused a problem? The bug report shows a situation that has a clear presumed behaviour, and could surely have just been fixed. Almost all my API's are dynamically bound to foreign code, eg: extern(C) void function( ref const(Vector2) v0, ref const(Vector2) v1, ref const(Vector2) v2, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled ) fillTriangle2D; extern(C) void function( ref const(Vector3) v0, ref const(Vector3) v1, ref const(Vector3) v2, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled ) fillTriangle; extern(C) void function( ref const(Vector2) vPosition, ref const(Vector2) vSizeref, ref BaseTexture texture, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled, TextureMode textureMode = TextureMode.AllChannels ) fillSprite; extern(C) void function( ref const(Vector2) vMin, ref const(Vector2) vMax, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled ) fillRectangle; extern(C) void function( ref const(Vector3) vMin, ref const(Vector3) vMax, ref const(Matrix4x3) mLocalToWorld = Matrix4x3.identity, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled ) fillBox; extern(C) void function( ref const(Vector3) vCenter, float fRadius, ref const(Matrix4x3) mLocalToWorld = Matrix4x3.identity, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled, int iTessellationX = 12, int iTessellationY = 8 ) fillSphere; extern(C) void function( float fLength, float fRadius, ref const(Matrix4x3) mLocalToWorld = Matrix4x3.identity, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled, int iTessellation = 10 ) fillCone; extern(C) void function( float fLength, float fRadius1, float fRadius2, ref const(Matrix4x3) mLocalToWorld = Matrix4x3.identity, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled, int iTessellation = 10 ) fillCylinder; extern(C) void function( ref const(Matrix4) mClipToWorld, ref const(Color) color = Color.white, BlendMode blendMode = BlendMode.Disabled ) fillFrustum; Functions in structs is common, default args are often convenient: struct MFMaterialInterface { extern (C) int function(void* pPlatformData) registerMaterial; extern (C) void function() unregisterMaterial; extern (C) void function(MFMaterial* pMaterial, MFMaterialCreateParams *pCreateParams = null) createInstance; extern (C) void function(MFMaterial* pMaterial) destroyInstance; extern (C) int function() getNumParams; extern (C) MFMaterialParameterInfo* function(int parameterIndex) getParameterInfo; extern (C) void function(MFMaterial* pMaterial, int parameterIndex, int argIndex, size_t value) setParameter; extern (C) size_t function(MFMaterial* pMaterial, int parameterIndex, int argIndex, void* pValue = null) getParameter; } extern (C) void function(const(char)* pName, const(MFMaterialInterface)* pInterface) MFMaterial_RegisterMaterialType; This isn't the best demonstration of this pattern, just a compact one. But function pointers in structs/classes is not unusual. Here's an advanced trick I use a lot since D doesn't extern to static C++ methods (heavily simplified, this is way out of context): struct CPPClass { this() { // not my actual code, but effectively, write 'this' and the C++ method pointer into a delegate on initialisation [I wrap this process up using magic] void** pDelegate = cast(void**)&cppNonVirtualMethod; pDelegate[0] = this; pDelegate[1] = pCPPMethodPointer; } void delegate(int x = 0) cppNonVirtualMethod; // C++ methods often have default args private: // C++ method pointer received from foreign code during initialisation static void* pCPPMethodPointer; }
Aug 27 2012
next sibling parent reply "Carl Sturtivant" <sturtivant gmail.com> writes:
 extern(C) void function( ref const(Vector2) v0, ref 
 const(Vector2) v1, ref const(Vector2) v2, ref const(Color) 
 color = Color.white, BlendMode blendMode = BlendMode.Disabled ) 
 fillTriangle2D;
If function pointers could be called with fewer than the prototypical number of arguments, and the remaining arguments be always initialized to their .init defaults, you could perhaps make this sort of thing work without the default argument values by using struct defaults. How would that be deficient?
Aug 27 2012
parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 14:08, Carl Sturtivant <sturtivant gmail.com> wrote:

 extern(C) void function( ref const(Vector2) v0, ref const(Vector2) v1,
 ref const(Vector2) v2, ref const(Color) color = Color.white, BlendMode
 blendMode = BlendMode.Disabled ) fillTriangle2D;
If function pointers could be called with fewer than the prototypical number of arguments, and the remaining arguments be always initialized to their .init defaults, you could perhaps make this sort of thing work without the default argument values by using struct defaults. How would that be deficient?
... no. Color does not .init == Color.white. You're suggesting I define a new type, obscuring the API, every time I want a non-.init default arg? Also, I think it's correct that functions shouldn't be callable without explicit parameters. Default args are carefully selected, and they are always opt-in. 'v2' in this case shouldn't be allowed to default to [ NaN, NaN ] if I omit it.
Aug 27 2012
next sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 12:14:30 UTC, Manu wrote:
 On 27 August 2012 14:08, Carl Sturtivant <sturtivant gmail.com> 
 wrote:

 extern(C) void function( ref const(Vector2) v0, ref 
 const(Vector2) v1,
 ref const(Vector2) v2, ref const(Color) color = Color.white, 
 BlendMode
 blendMode = BlendMode.Disabled ) fillTriangle2D;
If function pointers could be called with fewer than the prototypical number of arguments, and the remaining arguments be always initialized to their .init defaults, you could perhaps make this sort of thing work without the default argument values by using struct defaults. How would that be deficient?
... no. Color does not .init == Color.white. You're suggesting I define a new type, obscuring the API, every time I want a non-.init default arg? Also, I think it's correct that functions shouldn't be callable without explicit parameters. Default args are carefully selected, and they are always opt-in. 'v2' in this case shouldn't be allowed to default to [ NaN, NaN ] if I omit it.
Yes, I am suggesting that to get the default you want you define a new (free at runtime) type. You need the D declaration of whichever C struct anyway, so this is that declaration with a default added. In a more complex situation aliasing can be used. For e.g. doubles, you can play a suitable struct game for once and for all, and use it repeatedly inside the D version of C structs. And I am suggesting the language definition be changed to allow calls of function pointers be made with some trailing arguments omitted. If default arguments for function pointers are not available, except for the default defaults (.init), then what of this proposal?
Aug 27 2012
prev sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Monday, 27 August 2012 at 12:14:30 UTC, Manu wrote:
 On 27 August 2012 14:08, Carl Sturtivant <sturtivant gmail.com> 
 wrote:

 extern(C) void function( ref const(Vector2) v0, ref 
 const(Vector2) v1,
 ref const(Vector2) v2, ref const(Color) color = Color.white, 
 BlendMode
 blendMode = BlendMode.Disabled ) fillTriangle2D;
If function pointers could be called with fewer than the prototypical number of arguments, and the remaining arguments be always initialized to their .init defaults, you could perhaps make this sort of thing work without the default argument values by using struct defaults. How would that be deficient?
... no. Color does not .init == Color.white. You're suggesting I define a new type, obscuring the API, every time I want a non-.init default arg? Also, I think it's correct that functions shouldn't be callable without explicit parameters. Default args are carefully selected, and they are always opt-in. 'v2' in this case shouldn't be allowed to default to [ NaN, NaN ] if I omit it.
More specifically, you can get the default initialization of e.g. double to be different as follows. import std.stdio; struct dbl(double init) { double d = init; alias d this; } void main() { dbl!3.142 x; dbl!2.0 y; writeln(x*y); } In the case of an external(C) struct, the D definition can fix up its own default initialization without a new type name even. Color for example could be fixed up to initialize to Color.white by default. And, having the compiler fix up omitted trailing arguments with .init default values could be confined to "opt-in" parameters by a small change to the type system for function pointers as follows. The type of a function pointer could now include the number of trailing parameters that may be defaulted. This does not explode the size of a mangled name much. A syntactic mechanism to indicate which trailing parameters may be defaulted in a function prototype could be added to D. The default would be none with functions declared without that mechanism. This mechanism needs to be a part of the prototype so that it can be used on external(C) functions etcetera. Now only trailing parameters that you intend to be defaulted would would get defaulted with the .init treatment. Others would be compilation errors. Now what's wrong with this if there are no trailing argument defaults except for the .init defaults?
Aug 28 2012
prev sibling next sibling parent reply "Robert Clipsham" <robert octarineparrot.com> writes:
On Monday, 27 August 2012 at 10:32:28 UTC, Manu wrote:
 Because the two types were considered to be the same, only 
 different.
And how was that a problem? They never interacted in the example, the assignments were totally separate, they shouldn't have been confused. Just speculating, but it just looks like the type was misrepresented when it was looked up from a map by name or something, and matched the wrong cached definition... or something along those lines. It looks like a bug exposed from implementation detail, I can't see anything in the bug report that shouldn't theoretically work fine.
I seem to recall I looked at this issue myself at one point. It goes something like: ---- auto foo = (int a = 1) { return a; }; auto bar = (int a) { return a; }; ---- int function(int) is mangled exactly the same as int function(int = 1) as default args aren't used for mangling. dmd does semantic analysis on the type of foo, which returns int function(int = 1), which is mangled as int function(int) and stored in dmd's hashmap of types (default args aren't mangled). When the semantic analysis of bar is done it checks the hashmap, sees that the type is already there (has the same name mangling) and does not repeat semantic analysis. If you switch the order of declarations then the opposite happens - the default arg is ignored.
Aug 27 2012
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 02:48 PM, Robert Clipsham wrote:
 On Monday, 27 August 2012 at 10:32:28 UTC, Manu wrote:
 Because the two types were considered to be the same, only different.
And how was that a problem? They never interacted in the example, the assignments were totally separate, they shouldn't have been confused. Just speculating, but it just looks like the type was misrepresented when it was looked up from a map by name or something, and matched the wrong cached definition... or something along those lines. It looks like a bug exposed from implementation detail, I can't see anything in the bug report that shouldn't theoretically work fine.
I seem to recall I looked at this issue myself at one point. It goes something like: ---- auto foo = (int a = 1) { return a; }; auto bar = (int a) { return a; }; ---- int function(int) is mangled exactly the same as int function(int = 1) as default args aren't used for mangling. dmd does semantic analysis on the type of foo, which returns int function(int = 1), which is mangled as int function(int) and stored in dmd's hashmap of types (default args aren't mangled). When the semantic analysis of bar is done it checks the hashmap, sees that the type is already there (has the same name mangling) and does not repeat semantic analysis. If you switch the order of declarations then the opposite happens - the default arg is ignored.
If the compiler design *requires* equal types to be stored uniquely in a hash map, then the default args shouldn't be stored as part of the type in the AST. I assume it is rather inconvenient to fix, but certainly possible.
Aug 27 2012
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 6:05 AM, Timon Gehr wrote:
 On 08/27/2012 02:48 PM, Robert Clipsham wrote:
 I seem to recall I looked at this issue myself at one point. It goes
 something like:
 ----
 auto foo = (int a = 1) { return a; };
 auto bar = (int a) { return a; };
 ----
 int function(int) is mangled exactly the same as int function(int = 1)
 as default args aren't used for mangling. dmd does semantic analysis on
 the type of foo, which returns int function(int = 1), which is mangled
 as int function(int) and stored in dmd's hashmap of types (default args
 aren't mangled). When the semantic analysis of bar is done it checks the
 hashmap, sees that the type is already there (has the same name
 mangling) and does not repeat semantic analysis. If you switch the order
 of declarations then the opposite happens - the default arg is ignored.
This is correct.
 If the compiler design *requires* equal types to be stored uniquely in
 a hash map, then the default args shouldn't be stored as part of the
 type in the AST. I assume it is rather inconvenient to fix, but
 certainly possible.
The language design requires a 1:1 mapping of mangling to types. Hence the compiler design to use the mangling as a hashmap key of types. The failure of that approach in this case points to a problem in the language design, not a bug in the compiler.
Aug 27 2012
parent reply Jacob Carlborg <doob me.com> writes:
On 2012-08-27 22:53, Walter Bright wrote:

 The language design requires a 1:1 mapping of mangling to types. Hence
 the compiler design to use the mangling as a hashmap key of types. The
 failure of that approach in this case points to a problem in the
 language design, not a bug in the compiler.
How does this then work when the body of the anonymous functions are different? How will they be identified? -- /Jacob Carlborg
Aug 27 2012
next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 11:53 PM, Jacob Carlborg wrote:
 How does this then work when the body of the anonymous functions are different?
 How will they be identified?
I don't know what you mean.
Aug 28 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-08-28 09:16, Walter Bright wrote:

 I don't know what you mean.
The original problem in the bug report looked like this: void main () { auto foo = (int a = 1) { return a; }; auto bar = (int a) { return a; }; writeln(foo()); writeln(bar()); } If I change one of the bodies of the anonymous functions to look like this: void main () { auto foo = (int a = 1) { return a; writeln("foo"); }; auto bar = (int a) { return a; }; writeln(foo()); writeln(bar()); } It behaves correctly and won't compile any more. -- /Jacob Carlborg
Aug 28 2012
prev sibling parent reply "Robert Clipsham" <robert octarineparrot.com> writes:
On Tuesday, 28 August 2012 at 06:53:15 UTC, Jacob Carlborg wrote:
 On 2012-08-27 22:53, Walter Bright wrote:

 The language design requires a 1:1 mapping of mangling to 
 types. Hence
 the compiler design to use the mangling as a hashmap key of 
 types. The
 failure of that approach in this case points to a problem in 
 the
 language design, not a bug in the compiler.
How does this then work when the body of the anonymous functions are different? How will they be identified?
The body of the function has nothing to do with its type.
Aug 28 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-08-28 21:27, Robert Clipsham wrote:

 The body of the function has nothing to do with its type.
If I change the body of one of the functions the problem doesn't appear. -- /Jacob Carlborg
Aug 29 2012
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 15:48, Robert Clipsham <robert octarineparrot.com> wrote:

 I seem to recall I looked at this issue myself at one point. It goes
 something like:
 ----

 auto foo = (int a = 1) { return a; };
 auto bar = (int a) { return a; };
 ----
 int function(int) is mangled exactly the same as int function(int = 1) as
 default args aren't used for mangling. dmd does semantic analysis on the
 type of foo, which returns int function(int = 1), which is mangled as int
 function(int) and stored in dmd's hashmap of types (default args aren't
 mangled). When the semantic analysis of bar is done it checks the hashmap,
 sees that the type is already there (has the same name mangling) and does
 not repeat semantic analysis. If you switch the order of declarations then
 the opposite happens - the default arg is ignored.
Cached in a hashmap! precisely what I suspected (without knowing anything about it) ;) That explains why Walter keeps going on about the name mangling. It's all clear.
Aug 27 2012
parent reply "foobar" <foo bar.com> writes:
On Monday, 27 August 2012 at 13:44:43 UTC, Manu wrote:
 On 27 August 2012 15:48, Robert Clipsham 
 <robert octarineparrot.com> wrote:

 I seem to recall I looked at this issue myself at one point. 
 It goes
 something like:
 ----

 auto foo = (int a = 1) { return a; };
 auto bar = (int a) { return a; };
 ----
 int function(int) is mangled exactly the same as int 
 function(int = 1) as
 default args aren't used for mangling. dmd does semantic 
 analysis on the
 type of foo, which returns int function(int = 1), which is 
 mangled as int
 function(int) and stored in dmd's hashmap of types (default 
 args aren't
 mangled). When the semantic analysis of bar is done it checks 
 the hashmap,
 sees that the type is already there (has the same name 
 mangling) and does
 not repeat semantic analysis. If you switch the order of 
 declarations then
 the opposite happens - the default arg is ignored.
Cached in a hashmap! precisely what I suspected (without knowing anything about it) ;) That explains why Walter keeps going on about the name mangling. It's all clear.
This discussion is all sorts of wrong. Whoever said that defargs are metadata (Manu?) was right. Therefore it would make sense to implement a general metadata facility for D and use *that* for defargs. C++11 has annotations, so that's the place to start looking at. D has already enough of those pesky special case features, let's not add yet another one at the expense of a more general metadata/annotation mechanism which eventually would be added anyway due to popular demand.
Aug 27 2012
next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 17:03, foobar <foo bar.com> wrote:

 This discussion is all sorts of wrong. Whoever said that defargs are
 metadata (Manu?) was right. Therefore it would make sense to implement a
 general metadata facility for D and use *that* for defargs. C++11 has
 annotations, so that's the place to start looking at. D has already enough
 of those pesky special case features, let's not add yet another one at the
 expense of a more general metadata/annotation mechanism which eventually
 would be added anyway due to popular demand.
Yes, I suggested this as a possibility above, but nobody commented. It seems like it might solve 2 problems with one stone ;)
Aug 27 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/27/12 7:34 AM, Manu wrote:
 On 27 August 2012 17:03, foobar <foo bar.com <mailto:foo bar.com>> wrote:

     This discussion is all sorts of wrong. Whoever said that defargs are
     metadata (Manu?) was right. Therefore it would make sense to
     implement a general metadata facility for D and use *that* for
     defargs. C++11 has annotations, so that's the place to start looking
     at. D has already enough of those pesky special case features, let's
     not add yet another one at the expense of a more general
     metadata/annotation mechanism which eventually would be added anyway
     due to popular demand.


 Yes, I suggested this as a possibility above, but nobody commented. It
 seems like it might solve 2 problems with one stone ;)
Whilst I agree a metadata facility is an interesting topic to look into, I think default arguments for functions is a poor motivating example. Andrei
Aug 27 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 04:53 PM, Andrei Alexandrescu wrote:
 On 8/27/12 7:34 AM, Manu wrote:
 On 27 August 2012 17:03, foobar <foo bar.com <mailto:foo bar.com>> wrote:

     This discussion is all sorts of wrong. Whoever said that defargs are
     metadata (Manu?) was right. Therefore it would make sense to
     implement a general metadata facility for D and use *that* for
     defargs. C++11 has annotations, so that's the place to start looking
     at. D has already enough of those pesky special case features, let's
     not add yet another one at the expense of a more general
     metadata/annotation mechanism which eventually would be added anyway
     due to popular demand.


 Yes, I suggested this as a possibility above, but nobody commented. It
 seems like it might solve 2 problems with one stone ;)
Whilst I agree a metadata facility is an interesting topic to look into, I think default arguments for functions is a poor motivating example. Andrei
+1.
Aug 27 2012
prev sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 27 August 2012 at 14:53:24 UTC, Andrei Alexandrescu 
wrote:
 On 8/27/12 7:34 AM, Manu wrote:
 On 27 August 2012 17:03, foobar <foo bar.com 
 <mailto:foo bar.com>> wrote:

    This discussion is all sorts of wrong. Whoever said that 
 defargs are
    metadata (Manu?) was right. Therefore it would make sense to
    implement a general metadata facility for D and use *that* 
 for
    defargs. C++11 has annotations, so that's the place to 
 start looking
    at. D has already enough of those pesky special case 
 features, let's
    not add yet another one at the expense of a more general
    metadata/annotation mechanism which eventually would be 
 added anyway
    due to popular demand.


 Yes, I suggested this as a possibility above, but nobody 
 commented. It
 seems like it might solve 2 problems with one stone ;)
Whilst I agree a metadata facility is an interesting topic to look into, I think default arguments for functions is a poor motivating example. Andrei
The point was that there are _other_ motivating examples for annotations hence there's already popular demand for it. Which is why it's worth the added complexity to the language. In fact part of that complexity *already* exists in D syntax via built-in annotations (e.g safe). This same feature (annotations) can be leveraged to solve this small corner case hence it shouldn't be implemented separately and complicate both D and DMD for little gain.
Aug 27 2012
parent reply Marco Leise <Marco.Leise gmx.de> writes:
Am Mon, 27 Aug 2012 19:46:57 +0200
schrieb "foobar" <foo bar.com>:

 The point was that there are _other_ motivating examples for 
 annotations hence there's already popular demand for it. Which is 
 why it's worth the added complexity to the language. In fact part 
 of that complexity *already* exists in D syntax via built-in 
 annotations (e.g  safe).
Properties are currently implemented as "strip off the and continue". They are mapped to storage class bit flags, like the others with no special code paths or data structures in place. -- Marco
Aug 28 2012
parent "foobar" <foo bar.com> writes:
On Wednesday, 29 August 2012 at 07:00:06 UTC, Marco Leise wrote:
 Am Mon, 27 Aug 2012 19:46:57 +0200
 schrieb "foobar" <foo bar.com>:

 The point was that there are _other_ motivating examples for 
 annotations hence there's already popular demand for it. Which 
 is why it's worth the added complexity to the language. In 
 fact part of that complexity *already* exists in D syntax via 
 built-in annotations (e.g  safe).
Properties are currently implemented as "strip off the and continue". They are mapped to storage class bit flags, like the others with no special code paths or data structures in place.
I was talking about complexity added to _the language_ and its syntax - the part exposed to the user. I did not mean to imply anything about the compiler implementation of it. If what you say is true, it just shows that those features such as pure shouldn't have got the prefix in the first place. Anyway, my main point was: a. There already is popular demand for metadata. b. We already have some of the desired syntax for it in the language c. Once the facility is fully implemented and supports user defined annotations it could also be leveraged to implement default arguments as well.
Aug 29 2012
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 7:03 AM, foobar wrote:
 This discussion is all sorts of wrong. Whoever said that defargs are metadata
 (Manu?) was right. Therefore it would make sense to implement a general
metadata
 facility for D and use *that* for defargs. C++11 has annotations, so that's the
 place to start looking at. D has already enough of those pesky special case
 features, let's not add yet another one at the expense of a more general
 metadata/annotation mechanism which eventually would be added anyway due to
 popular demand.
The annotations design discussed was about annotating declarations, not types.
Aug 27 2012
parent Manu <turkeyman gmail.com> writes:
On 27 August 2012 23:54, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/27/2012 7:03 AM, foobar wrote:

 This discussion is all sorts of wrong. Whoever said that defargs are
 metadata
 (Manu?) was right. Therefore it would make sense to implement a general
 metadata
 facility for D and use *that* for defargs. C++11 has annotations, so
 that's the
 place to start looking at. D has already enough of those pesky special
 case
 features, let's not add yet another one at the expense of a more general
 metadata/annotation mechanism which eventually would be added anyway due
 to
 popular demand.
The annotations design discussed was about annotating declarations, not types.
And that may be fine for default args too. I've personally only ever used them where I've explicitly defined them.
Aug 27 2012
prev sibling parent =?UTF-8?B?QWxpIMOHZWhyZWxp?= <acehreli yahoo.com> writes:
On 08/27/2012 05:48 AM, Robert Clipsham wrote:

 I seem to recall I looked at this issue myself at one point. It goes
 something like:
 ----
 auto foo = (int a = 1) { return a; };
 auto bar = (int a) { return a; };
 ----
 int function(int) is mangled exactly the same as int function(int = 1)
 as default args aren't used for mangling. dmd does semantic analysis on
 the type of foo, which returns int function(int = 1), which is mangled
 as int function(int) and stored in dmd's hashmap of types (default args
 aren't mangled). When the semantic analysis of bar is done it checks the
 hashmap, sees that the type is already there (has the same name
 mangling) and does not repeat semantic analysis. If you switch the order
 of declarations then the opposite happens - the default arg is ignored.
I had opened a related bug recently: http://d.puremagic.com/issues/show_bug.cgi?id=8579 Ali
Aug 27 2012
prev sibling next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 12:32 PM, Manu wrote:
 On 27 August 2012 11:28, Walter Bright <newshound2 digitalmars.com
 <mailto:newshound2 digitalmars.com>> wrote:

     On 8/27/2012 1:08 AM, Manu wrote:


         Also, I think it could be fixed so the scenario in the bug
         report worked as
         expected (I still don't understand why it did't work in the
         first place).


     Because the two types were considered to be the same, only different.


 And how was that a problem? They never interacted in the example, the
 assignments were totally separate, they shouldn't have been confused.
 Just speculating, but it just looks like the type was misrepresented
 when it was looked up from a map by name or something, and matched the
 wrong cached definition... or something along those lines.
 It looks like a bug exposed from implementation detail, I can't see
 anything in the bug report that shouldn't theoretically work fine.
 ...
+1.
Aug 27 2012
prev sibling next sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 3:32 AM, Manu wrote:
 Well likewise, can you provide an example where, assuming that one bug were
 fixed, that the old approach actually caused a problem?
 The bug report shows a situation that has a clear presumed behaviour, and could
 surely have just been fixed.
Yes, I can make that example "work", but it would not make the general case work. It would just kick the can down the road, and I'd get more bug reports on those. I'll look at your example separately.
Aug 27 2012
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 3:32 AM, Manu wrote:
 Here's an advanced trick I use a lot since D doesn't extern to static C++
 methods (heavily simplified, this is way out of context):

 struct CPPClass
 {
      this()
      {
          // not my actual code, but effectively, write 'this' and the C++
method
 pointer into a delegate on initialisation [I wrap this process up using magic]
          void** pDelegate = cast(void**)&cppNonVirtualMethod;
          pDelegate[0] = this;
          pDelegate[1] = pCPPMethodPointer;
      }

      void delegate(int x = 0) cppNonVirtualMethod; // C++ methods often have
 default args
void delegate(int x) cppNonVirtualMethod; void callCppNonVirtualMethod(int x) { (*cppNonVirtualMethod)(x); } void callCppNonVirtualMethod() { callCppNonVirtualMethod(0); } With inlining on, the calls to the second overload should disappear, and you have the same code generated as you would for the default arg method. You could probably reduce the typing with some sort of mixin template, but this is the basic idea.
 private:
      // C++ method pointer received from foreign code during initialisation
      static void* pCPPMethodPointer;
 }
Aug 27 2012
parent Manu <turkeyman gmail.com> writes:
On 28 August 2012 00:03, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/27/2012 3:32 AM, Manu wrote:

 Here's an advanced trick I use a lot since D doesn't extern to static C++
 methods (heavily simplified, this is way out of context):

 struct CPPClass
 {
      this()
      {
          // not my actual code, but effectively, write 'this' and the C++
 method
 pointer into a delegate on initialisation [I wrap this process up using
 magic]
          void** pDelegate = cast(void**)&**cppNonVirtualMethod;
          pDelegate[0] = this;
          pDelegate[1] = pCPPMethodPointer;
      }

      void delegate(int x = 0) cppNonVirtualMethod; // C++ methods often
 have
 default args
void delegate(int x) cppNonVirtualMethod; void callCppNonVirtualMethod(int x) { (*cppNonVirtualMethod)(x); } void callCppNonVirtualMethod() { callCppNonVirtualMethod(0); } With inlining on, the calls to the second overload should disappear, and you have the same code generated as you would for the default arg method. You could probably reduce the typing with some sort of mixin template, but this is the basic idea.
I've actually considered this approach, and the overload isn't required in this case, since the true method can have a default arg. The same pattern can apply at the global scope. There are 2 reasons I didn't go for it: Every api call is a double-call with inlining turned off (debug builds). Debug builds are already slow enough, I didn't want to base a design decision on that when it was perfectly syntactically reasonable to call through the function pointers directly. __forceinline would address this concern. (I really need this anyway for the simd api. simd intrinsics are all wrapped in templates; if these are not inlined, making a function call for every math opcode, it's gonna be waaaaay slower than using the fpu) And the second is forward declarations and function definitiona appearing within the same file (I have a feature request on this). The API is easily defined by the function pointer, but since the definition needs to define the default args, what now needs to be defined is a forward declaration/prototype. The trouble is, my module magic mixin can't produce the bodies for the stubs it finds because a prototype and definition can't appear in the same file. void functionDecl(int x = 10); // declaration by user (ideally with an custom attribute: engine_import, which would inform the magic generating mixin to produce a stub and linkage code for this) // **** magically generated by a mixin: **** void function(int) __functionDecl_ptr; __forceinline void functionDecl(int x) { __functionDecl_ptr(x); } // ************** static this() { __functionDecl_ptr = importExtern("functionDecl"); } // ************** Sadly, there's a lot missing from the language to be able to do that, but I hope we can reach that place one day.
Aug 27 2012
prev sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 3:32 AM, Manu wrote:
 Almost all my API's are dynamically bound to foreign code, eg:

 extern(C) void function( ref const(Vector2) v0, ref const(Vector2) v1, ref
 const(Vector2) v2, ref const(Color) color = Color.white, BlendMode blendMode =
 BlendMode.Disabled ) fillTriangle2D;
These can all be handled in a straightforward (but admittedly wordy) method of wrapping the call to the function pointer in an overloaded function. I understand that you've probably got so many of these, it's drudgery to make the edits. Question: are the default values all of the form T.init? I.e. is Color.white == Color.init?
Aug 27 2012
parent reply Manu <turkeyman gmail.com> writes:
On 28 August 2012 00:11, Walter Bright <newshound2 digitalmars.com> wrote:

 On 8/27/2012 3:32 AM, Manu wrote:

 Almost all my API's are dynamically bound to foreign code, eg:

 extern(C) void function( ref const(Vector2) v0, ref const(Vector2) v1, ref
 const(Vector2) v2, ref const(Color) color = Color.white, BlendMode
 blendMode =
 BlendMode.Disabled ) fillTriangle2D;
These can all be handled in a straightforward (but admittedly wordy) method of wrapping the call to the function pointer in an overloaded function.
They can, see my prev post. It's less readable, less maintainable, more obscure to those reading the code... the language had an awesome feature that addressed it directly. When mixins get involved, readability, syntax highlighting, and other basic IDE services all go out the window. Perhaps the most important one that I forgot; debugging becomes VERY annoying. Try stepping into an engine API when it's wrapped up with magic... F10 (jumps to a rubbish API file, cursor disappears, stub code doesn't actually exist). Continue pressing F10 for a while until it eventually finds it's way into the function pointer. Repeat when leaving the function >_< .. Again, __forceinline would address this. I understand that you've probably got so many of these, it's drudgery to
 make the edits.
It would be annoying. Question: are the default values all of the form T.init? I.e. is
 Color.white == Color.init?
No, I don't think so. Color inits to black. Many enums also have an Unknown state. and float init's to NaN for some crazy reason ;)
Aug 27 2012
parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 2:37 PM, Manu wrote:
 They can, see my prev post.

 It's less readable, less maintainable, more obscure to those reading the
code...
 the language had an awesome feature that addressed it directly.
 When mixins get involved, readability, syntax highlighting, and other basic IDE
 services all go out the window.
Ok, thanks, it helps immensely to see just what you're trying to do. Let me think about it some more.
Aug 27 2012
prev sibling next sibling parent reply kenji hara <k.hara.pg gmail.com> writes:
I think that the function type and its mangled name must not contain
the default args, then I can agree with Walter at the point.

But, I think the variables of function pointers and delegates still
can have default args as a part of their declarations.
(It will be a part of VarDeclaration, not a part of TypeFunction)
In following case, the default arg looks like a part of the type, but
actually is a part of the declaration of fp.

void function(int n = 10) fp;
pragma(msg, typeof(fp));  // should print void function(int), because
default args are not a part of type.
fp();  // I think this can be allowed, because fp can remember that
the first parameter has the default argument.

But, it seems to me there are some corner cases.

  // fp will *inherit* default args from its initializer, or not?
  auto fp = (int n = 10){}

  // what is the actual default arg of fp?
  void function(int n = 10) fp1 = (int n = 20){}
  void function(int n) fp2 = (int n = 30){}
  void function(int n = 40) fp3 = (int n){}

  // fp has ambiguous default arg, or has no default arg?
  auto fp1 = some_runtime_condition ? (int n = 10){} : (int n = 20){} ;
  // more complicated case, first defarg is same, then it will be *inherited*?
  auto fp2 = some_runtime_condition ? (int n = 10, string s =
"hello"){} : (int n = 10, string s = "world"){} ;

  int function(int n = 10) fp;   // default arg of the first parameter is 10
  fp = (int n = 20){ return n; }  // function literal's default arg
will be ignored (in my opinion), is this expected?

  // returning function pointer/delegate type can have default args?
  int delegate(int n = 10) foo(int x) { ... }

If we can take agreements each other about them, it may be supported.

Kenji Hara

2012/8/27 Walter Bright <newshound2 digitalmars.com>:
 On 8/27/2012 1:08 AM, Manu wrote:
 Does the bug report actually demonstrate how it was causing anybody any
 problems? It seemed a rather contrived scenario that just illustrated that
 there
 was a bug.
It was probably reduced from a larger scenario. Reduced bugs usually look pretty twisted.
 Also, I think it could be fixed so the scenario in the bug report worked
 as
 expected (I still don't understand why it did't work in the first place).
Because the two types were considered to be the same, only different. ---------------------------------- Please post a canonical example of how you use this, so we can think of an alternative.
Aug 27 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08/27/2012 03:23 PM, kenji hara wrote:
 I think that the function type and its mangled name must not contain
 the default args, then I can agree with Walter at the point.

 But, I think the variables of function pointers and delegates still
 can have default args as a part of their declarations.
 (It will be a part of VarDeclaration, not a part of TypeFunction)
 In following case, the default arg looks like a part of the type, but
 actually is a part of the declaration of fp.

 void function(int n = 10) fp;
 pragma(msg, typeof(fp));  // should print void function(int), because
 default args are not a part of type.
 fp();  // I think this can be allowed, because fp can remember that
 the first parameter has the default argument.

 But, it seems to me there are some corner cases.

    // fp will *inherit* default args from its initializer, or not?
    auto fp = (int n = 10){}
I assume we'd like it to do that lest the user has to supply the type explicitly. It might lead to funny interactions though.
    // what is the actual default arg of fp?
    void function(int n = 10) fp1 = (int n = 20){}
    void function(int n) fp2 = (int n = 30){}
    void function(int n = 40) fp3 = (int n){}
The declared default arg overrides. I'd suggest to make it an error when it can be fixed by leaving out the default argument from a function literal occurring within the expression.
    // fp has ambiguous default arg, or has no default arg?
    auto fp1 = some_runtime_condition ? (int n = 10){} : (int n = 20){} ;
    // more complicated case, first defarg is same, then it will be *inherited*?
    auto fp2 = some_runtime_condition ? (int n = 10, string s =
 "hello"){} : (int n = 10, string s = "world"){} ;
I'd say, never attempt to combine default arguments in any way. It is not required to make it a hard error in all cases, because the error will occur when the user attempts to rely upon the non-existent default argument. By the argument from above, both of those should be in error.
    int function(int n = 10) fp;   // default arg of the first parameter is 10
    fp = (int n = 20){ return n; }  // function literal's default arg
 will be ignored (in my opinion), is this expected?
This specific expression should be in error, but it should probably be possible to do static int foo(int n=10){ return n; } int function(int = 20) fp = &foo; The reason why this should work is because adding a default argument should be as unlikely as possible to break existing code.
    // returning function pointer/delegate type can have default args?
    int delegate(int n = 10) foo(int x) { ... }
Seems easy enough to support for consistency.
 If we can take agreements each other about them, it may be supported.
I personally do not rely on them.
Aug 27 2012
prev sibling parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 27 Aug 2012 09:23:54 -0400, kenji hara <k.hara.pg gmail.com> wrote:

 I think that the function type and its mangled name must not contain
 the default args, then I can agree with Walter at the point.

 But, I think the variables of function pointers and delegates still
 can have default args as a part of their declarations.
 (It will be a part of VarDeclaration, not a part of TypeFunction)
 In following case, the default arg looks like a part of the type, but
 actually is a part of the declaration of fp.
I think the type of the function pointer and the type (and mangling) of the function are two separate types. One is a function pointer, one is a function. You only need to mangle the function, not the pointer type. Consider this rough equivalent (not sure how to write the constraints): struct callWithN(F, int n) if(F is a function which takes an int as its last arg) { F funcptr; auto opCall(T...)(T t) if(T is equivalent to F's args) { return funcptr(t); } auto opCall(T...)(T t) if(T is equivalent to F's args, except for last int arg) { return funcptr(t, n); } } I think the above (with correct template constraints) is equivalent to defining a function pointer with a default integer argument. Note that the function it points to has *nothing* to do with default args, and so should be mangled like any other function. So I think the default args have to be part of the pointer type, just not the function type.
 void function(int n = 10) fp;
 pragma(msg, typeof(fp));  // should print void function(int), because
 default args are not a part of type.
No. They are part of the function pointer type.
   // fp will *inherit* default args from its initializer, or not?
   auto fp = (int n = 10){}
Yes, this should be equivalent to: void function(int = 10) fp = (int n){};
   // what is the actual default arg of fp?
   void function(int n = 10) fp1 = (int n = 20){}
compiler error. Even if it should be able to reassign a default arg 10 to a default arg 20 function pointer, allowing the above to compile would be extremely confusing, and there is no real reason to allow it. Use the property of the function pointer that gets the function pointer type without the default params (see below).
   void function(int n) fp2 = (int n = 30){}
30
   void function(int n = 40) fp3 = (int n){}
40
   // fp has ambiguous default arg, or has no default arg?
   auto fp1 = some_runtime_condition ? (int n = 10){} : (int n = 20){} ;
Compiler error. You can't define the type differently based on a runtime condition.
   // more complicated case, first defarg is same, then it will be  
 *inherited*?
   auto fp2 = some_runtime_condition ? (int n = 10, string s =
 "hello"){} : (int n = 10, string s = "world"){} ;
Again, two different types. Error.
   int function(int n = 10) fp;   // default arg of the first parameter  
 is 10
   fp = (int n = 20){ return n; }  // function literal's default arg
 will be ignored (in my opinion), is this expected?
I think actually, this should be a compiler error, but it would be a valid choice to make this simply ignore the secondary default arg. Here is what I would like to happen: Given two sets of default parameter tuples X and Y, where X is not a prefix of Y, and Y is not a prefix of X, a function pointer with default args X cannot be assigned from a function pointer with default args Y. Given X and Y where X is a prefix of Y, a function pointer with default args Y can be assigned from a function pointer with default args X. However, the opposite will not be true. However, we should define a function pointer property (e.g. funcptr) which gives naked access to the underlying function pointer. So if you had fx with default args X, and fy with default args Y, and both pointed at functions with the same parameter types, then you could do: fx.funcptr = fy.funcptr; fy.funcptr = fx.funcptr; If this is too complicated, then just allowing all functions with the same underlying function parameter types to be freely assignable also is a valid (but IMO more confusing) choice. Given the above, I think it would be prudent to have the default args returnable via a property or a __traits command. e.g.: auto tupleOfDefaultArgs = fx.defargs;
   // returning function pointer/delegate type can have default args?
   int delegate(int n = 10) foo(int x) { ... }
Of course. Default args are part of the pointer type. -Steve
Aug 27 2012
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 16:23, kenji hara <k.hara.pg gmail.com> wrote:

 I think that the function type and its mangled name must not contain
 the default args, then I can agree with Walter at the point.

 But, I think the variables of function pointers and delegates still
 can have default args as a part of their declarations.
 (It will be a part of VarDeclaration, not a part of TypeFunction)
 In following case, the default arg looks like a part of the type, but
 actually is a part of the declaration of fp.

 void function(int n = 10) fp;
 pragma(msg, typeof(fp));  // should print void function(int), because
 default args are not a part of type.
 fp();  // I think this can be allowed, because fp can remember that
 the first parameter has the default argument.

 But, it seems to me there are some corner cases.

   // fp will *inherit* default args from its initializer, or not?
   auto fp = (int n = 10){}
I'd say, will. // what is the actual default arg of fp?
   void function(int n = 10) fp1 = (int n = 20){}
void function(int n) fp2 = (int n = 30){}
   void function(int n = 40) fp3 = (int n){}
The declaration (on the left) is correct in all cases. It was explicitly stated when defining the variable. // fp has ambiguous default arg, or has no default arg?
   auto fp1 = some_runtime_condition ? (int n = 10){} : (int n = 20){} ;
Finally, a REAL conundrum! :) .. Not sure what to tell you... Error? Warning, and remove the default arg since there's a disagreement?
   // more complicated case, first defarg is same, then it will be
 *inherited*?
   auto fp2 = some_runtime_condition ? (int n = 10, string s =
 "hello"){} : (int n = 10, string s = "world"){} ;
Same problem as above. I'd say hard error, since this is a) extremely unlikely! And b) it's obvious there's no 'correct' conclusion anyway. The user writing that code clearly imagines that D is a dynamic language, and doesn't properly understand the nature of a compiled language. int function(int n = 10) fp; // default arg of the first parameter is 10
   fp = (int n = 20){ return n; }  // function literal's default arg
 will be ignored (in my opinion), is this expected?
Agree, the variable was already defined by the users explicit request. // returning function pointer/delegate type can have default args?
   int delegate(int n = 10) foo(int x) { ... }
Sure, why not? If the recipient is auto, then it should inherit those properties. If we can take agreements each other about them, it may be supported.

+1 for Kenji :)
Aug 27 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/27/12 7:03 AM, Manu wrote:
     If we can take agreements each other about them, it may be supported.

 +1 for Kenji :)
I think the matter here is that we're looking at adding very significant complexity to the language for a feature with at best minor, marginal benefits. Default arguments add no power to the language, you can quote me on that. A wrong was fixed with a simplifying solution. That broke some code. Granted, the code could be made to work with a much more complicated (and complicating) solution. Is it worth it? At this point in D's development, do we want to go full-bore about something that complicates the language to very little benefit, particularly considering we already have so many balls in the air? I think it's reasonable to look into fixing the code and calling it a day. Thanks, Andrei
Aug 27 2012
parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 17:51, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org>wrote:

 On 8/27/12 7:03 AM, Manu wrote:

     If we can take agreements each other about them, it may be supported.

 +1 for Kenji :)
I think the matter here is that we're looking at adding very significant complexity to the language for a feature with at best minor, marginal benefits.
... what? We're not talking about *adding* anything, we're talking about a bug. The scenario given in the bug report isn't even theoretically flawed, it should work fine under the existing design (hence the bug report), but it seems some design changes may a) make more sense, and also b) simplify implementation/fixing the bug. That seems fine, but rather than fixing said implementation bug, the feature was just removed from the language, which was coincidentally a major breaking change. Does the dmd implementation define the D language? I'm sure there's lots of bugs in various features that could be solved by removing the feature completely. I encounter problems using 'ref' and 'in' all the time... The feature was obviously intended, someone took the time to put it there in the first place, and people use it. It's implementation is apparently not perfect, and needs to be clarified, maybe that requires a design tweak. That seems more what Kenji is talking about to me. Default arguments add no power to the language, you can quote me on that.

Did I mention that *every project* I've written in D is broken? Why have I
used a feature so much if it offers nothing?
The fact the bug even exists implies that someone saw value in the feature
in the first place...

I'm not sure how you define 'power', but default args certainly offer
convenience. It also allows the default to be changed universally in one
place.
I wouldn't want to find the location of all calls to the function, and then
reason whether their manual placement of the default arg at each instance
is as a conventional default (such that I should update it to the new
default), or whether they explicitly intend that value in this particular
case.


A wrong was fixed with a simplifying solution. That broke some code.
 Granted, the code could be made to work with a much more complicated (and
 complicating) solution. Is it worth it? At this point in D's development,
 do we want to go full-bore about something that complicates the language to
 very little benefit, particularly considering we already have so many balls
 in the air? I think it's reasonable to look into fixing the code and
 calling it a day.
A 'wrong'? What a subjective opinion. Default args are useful, and I absolutely appreciated the uniformity D seemed to offer in this respect (ie, all kinds of function definition supported them). How do you define 'fixing the code'? The 'fix' is a major breaking change. Usually people are extremely phobic of the possibility of breaking changes in even minor cases, yet this is relatively severe. Far more important changes have been rejected on the grounds that they're breaking changes alone... If a solid solution is too difficult to implement, or isn't worth the time right now, then just put the bug back. It was such a minor bug in a very contrived case, and better than the fix. On 27 August 2012 17:53, Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> wrote:
 On 8/27/12 7:34 AM, Manu wrote:

 Yes, I suggested this as a possibility above, but nobody commented. It
 seems like it might solve 2 problems with one stone ;)
Whilst I agree a metadata facility is an interesting topic to look into, I think default arguments for functions is a poor motivating example.
I agree. It was just an interesting parallel that I noticed.
Aug 27 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/27/12 8:46 AM, Manu wrote:
 We're not talking about /adding/ anything, we're talking about a bug.
 The scenario given in the bug report isn't even theoretically flawed, it
 should work fine under the existing design (hence the bug report), but
 it seems some design changes may a) make more sense, and also b)
 simplify implementation/fixing the bug.
My understanding is that the bug revealed serious incompleteness in the underlying design of the feature; fixing them properly would be a major effort that (in my opinion) is not justified by the gains from the feature.
 That seems fine, but rather than fixing said implementation bug, the
 feature was just removed from the language, which was coincidentally a
 major breaking change.
I agree that being a breaking change gives the issue a larger weight.
 Does the dmd implementation define the D language?
No, and I wouldn't know why one would think I asserted otherwise.
 I'm sure there's lots of bugs in various features that could be solved
 by removing the feature completely.
 I encounter problems using 'ref' and 'in' all the time...
The matter at hand here is the value of the feature in proportion to its difficulty of implementation, prevalence in code, etc. I don't think it would be e.g. realistic to drop ref for the language, although clearly it would simplify it.
 The feature was obviously intended, someone took the time to put it
 there in the first place, and people use it.
 It's implementation is apparently not perfect, and needs to be
 clarified, maybe that requires a design tweak. That seems more what
 Kenji is talking about to me.
It does. Also, Kenji found a variety of issues that have no clear solution. I think there's no need for me to argue that features that "almost work" are not a good way to go, even though they are useful when they work.
     Default arguments add no power to the language, you can quote me on
     that.


 Did I mention that _every project_ I've written in D is broken? Why have
 I used a feature so much if it offers nothing?
I agree that your use of the feature and the subsequent breakage are good arguments in favor of keeping the feature. Yet I know for experience the feature's benefits are just marginal, and I am appealing to you to look into alternative implementations using the rest of the D programming language. I am convince you should find some. (Again, I agree that having to change once working code remains a tall order.)
     A wrong was fixed with a simplifying solution. That broke some code.
     Granted, the code could be made to work with a much more complicated
     (and complicating) solution. Is it worth it? At this point in D's
     development, do we want to go full-bore about something that
     complicates the language to very little benefit, particularly
     considering we already have so many balls in the air? I think it's
     reasonable to look into fixing the code and calling it a day.


 A 'wrong'? What a subjective opinion.
Actually, no. By "wrong" I meant the bug itself.
 Default args are useful, and I
 absolutely appreciated the uniformity D seemed to offer in this respect
 (ie, all kinds of function definition supported them).
 How do you define 'fixing the code'? The 'fix' is a major breaking
 change. Usually people are extremely phobic of the possibility of
 breaking changes in even minor cases, yet this is relatively severe.

 Far more important changes have been rejected on the grounds that
 they're breaking changes alone...

 If a solid solution is too difficult to implement, or isn't worth the
 time right now, then just put the bug back. It was such a minor bug in a
 very contrived case, and better than the fix.
If experience with language design taught me anything, it is that contrived cases must be paid utmost attention. They are proofs that something is wrong. Andrei
Aug 27 2012
next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 27 Aug 2012 13:19:11 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 8/27/12 8:46 AM, Manu wrote:
 The feature was obviously intended, someone took the time to put it
 there in the first place, and people use it.
 It's implementation is apparently not perfect, and needs to be
 clarified, maybe that requires a design tweak. That seems more what
 Kenji is talking about to me.
It does. Also, Kenji found a variety of issues that have no clear solution. I think there's no need for me to argue that features that "almost work" are not a good way to go, even though they are useful when they work.
That's not true, Kenji asked how the feature should behave in certain cases, and I answered every one of them. There is a clear and valid solution. Whether it's worth implementing or not is another question. I think if it's not implemented we can come up with a very clunky library solution, but I'd rather see the compiler do it. -Steve
Aug 27 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/27/12 11:08 AM, Steven Schveighoffer wrote:
 It does. Also, Kenji found a variety of issues that have no clear
 solution. I think there's no need for me to argue that features that
 "almost work" are not a good way to go, even though they are useful
 when they work.
That's not true, Kenji asked how the feature should behave in certain cases, and I answered every one of them. There is a clear and valid solution.
What's missing is "simple". Andrei
Aug 27 2012
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Mon, 27 Aug 2012 14:45:54 -0400, Andrei Alexandrescu  
<SeeWebsiteForEmail erdani.org> wrote:

 On 8/27/12 11:08 AM, Steven Schveighoffer wrote:
 It does. Also, Kenji found a variety of issues that have no clear
 solution. I think there's no need for me to argue that features that
 "almost work" are not a good way to go, even though they are useful
 when they work.
That's not true, Kenji asked how the feature should behave in certain cases, and I answered every one of them. There is a clear and valid solution.
What's missing is "simple".
Simple as in simple to understand, or as in simple to implement? I think it's pretty simple to understand, especially if you understand default arguments in existing contexts. But that is a subjective measure. -Steve
Aug 27 2012
prev sibling parent deadalnix <deadalnix gmail.com> writes:
Le 27/08/2012 19:19, Andrei Alexandrescu a écrit :
 Does the dmd implementation define the D language?
No, and I wouldn't know why one would think I asserted otherwise.
You didn't, but I'd be foolish to assert otherwise.
Aug 27 2012
prev sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-08-27 17:46, Manu wrote:

 Does the dmd implementation define the D language?
No one really knows. It's probably a mix of dmd, dlang.org and TDPL. -- /Jacob Carlborg
Aug 27 2012
prev sibling parent reply deadalnix <deadalnix gmail.com> writes:
Le 27/08/2012 00:41, Walter Bright a écrit :
 On 8/26/2012 3:26 PM, Manu wrote:
 I just updated to 2.60 and found errors throughout my code where function
 pointers default args no longer work.
 _Every single project_ I've written in D, 5 projects, don't work anymore,
 including my projects at work.

 I found this discussion:
 http://d.puremagic.com/issues/show_bug.cgi?id=3866
 It seems the change was just decided and implemented with basically no
 discussion or argument at all :/

 My use cases are dynamic linkage, and cross-language integration.
 I can't manually interact with DLL's containing API's that expect to have
 default arguments if function pointers no longer support them.
 Also when receiving foreign language function pointers, they
 frequently need to
 have default args too.

 I also integrate with many C style API's (rendering engines and the
 like), which
 involve registration of various callbacks, and lots of those have
 default args too.

 I find this particularly surprising, since I recently motivated
 implementation
 of new traits which could parse default args from parameter lists, and
 use that
 to generation function pointers in templates which auto-magically clone
 functions parameter lists verbatim, specifically including the default
 args...
The trouble is that, as 3866 shows, there is no design anyone could come up with that worked in a consistent manner. The only consistent way out was to make default arguments a characteristic of the declaration, not of the type. The trouble for function pointers, is that any default args would need to be part of the type, not the declaration. I know it broke code (for many others, too), and I'm very sorry about that, but I don't see another way out. (Many uses of default arguments can be replaced with overloaded functions.)
That guy just have to rework 5 projects. This is quite a lot of work. Obviously, that change have to be done. But you can't just impose such a change on users. Their agenda isn't synchronized with D's. D NEED a better versioning scheme, as explained MANY times here already.
Aug 27 2012
parent reply "Maxim Fomin" <maxim maxim-fomin.ru> writes:
On Monday, 27 August 2012 at 20:29:15 UTC, deadalnix wrote:
 That guy just have to rework 5 projects. This is quite a lot of 
 work.

 Obviously, that change have to be done. But you can't just 
 impose such a change on users. Their agenda isn't synchronized 
 with D's.

 D NEED a better versioning scheme, as explained MANY times here 
 already.
I may be considered rude, but as I have understood from the first post Manu 1) decided to use a not fully designed language feature 2) used this feature in public API when working with DLLs (which is by itself a problematic area) 3) integrated this with other language in a a way that require substantial rewrite and possibly redesign 4) updated compiler which I consider a process of looking for problems and no excuse for making code-break change. Anyway, older version of compiler is available.
Aug 27 2012
next sibling parent deadalnix <deadalnix gmail.com> writes:
Le 27/08/2012 23:08, Maxim Fomin a écrit :
 On Monday, 27 August 2012 at 20:29:15 UTC, deadalnix wrote:
 That guy just have to rework 5 projects. This is quite a lot of work.

 Obviously, that change have to be done. But you can't just impose such
 a change on users. Their agenda isn't synchronized with D's.

 D NEED a better versioning scheme, as explained MANY times here already.
I may be considered rude, but as I have understood from the first post Manu 1) decided to use a not fully designed language feature 2) used this feature in public API when working with DLLs (which is by itself a problematic area) 3) integrated this with other language in a a way that require substantial rewrite and possibly redesign 4) updated compiler which I consider a process of looking for problems and no excuse for making code-break change. Anyway, older version of compiler is available.
The amount of corrected bug make the option of using an older compiler unrealistic.
Aug 27 2012
prev sibling next sibling parent reply "foobar" <foo bar.com> writes:
On Monday, 27 August 2012 at 21:08:48 UTC, Maxim Fomin wrote:
 On Monday, 27 August 2012 at 20:29:15 UTC, deadalnix wrote:
 That guy just have to rework 5 projects. This is quite a lot 
 of work.

 Obviously, that change have to be done. But you can't just 
 impose such a change on users. Their agenda isn't synchronized 
 with D's.

 D NEED a better versioning scheme, as explained MANY times 
 here already.
I may be considered rude, but as I have understood from the first post Manu 1) decided to use a not fully designed language feature 2) used this feature in public API when working with DLLs (which is by itself a problematic area) 3) integrated this with other language in a a way that require substantial rewrite and possibly redesign 4) updated compiler which I consider a process of looking for problems and no excuse for making code-break change. Anyway, older version of compiler is available.
All true, except one crucial fact: DMD gets critical bug fixes incorporated with new features in the same release. This leaves a poor choice to the programmer, either he sticks with older compiler version and can't get any critical bug fixes, or he updates the compiler to latest version with all the bug fixes but risks breakage of code due to new features (which is _exactly_ what happened to manu). It's high time for D to split its dev version from the stable one. All major software have this, Debian, Linux, browsers (chrome, Firefox, etc), Windows, etc, etc. This is an ancient concept practiced for decades now in the software world.
Aug 27 2012
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, August 27, 2012 23:22:39 foobar wrote:
 All true, except one crucial fact: DMD gets critical bug fixes
 incorporated with new features in the same release. This leaves a
 poor choice to the programmer, either he sticks with older
 compiler version and can't get any critical bug fixes, or he
 updates the compiler to latest version with all the bug fixes but
 risks breakage of code due to new features (which is _exactly_
 what happened to manu).
Except that the change which is causing Manu problems _isn't_ a new feature. It's a bug fix. So, better versioning wouldn't necessarily have helped him any at all. At best, if we had a more complex versioning scheme, it could be decided that the bug fix was potentially disruptive enough that it should only be fixed in a more major release, but _every_ bug fix risks breaking code, especially when code could be relying on the buggy behavior. - Jonathan M Davis
Aug 27 2012
next sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/27/12 2:28 PM, Jonathan M Davis wrote:
 On Monday, August 27, 2012 23:22:39 foobar wrote:
 All true, except one crucial fact: DMD gets critical bug fixes
 incorporated with new features in the same release. This leaves a
 poor choice to the programmer, either he sticks with older
 compiler version and can't get any critical bug fixes, or he
 updates the compiler to latest version with all the bug fixes but
 risks breakage of code due to new features (which is _exactly_
 what happened to manu).
Except that the change which is causing Manu problems _isn't_ a new feature. It's a bug fix. So, better versioning wouldn't necessarily have helped him any at all.
Yah, this particular example is more of an example of the limitation of the stable/dev approach. Nevertheless, I do think we should switch to it. But more importantly we must first move to an integrally automated, scripted method of releasing software. The amount of manual drudgery that goes into today's release process makes my blood curd. To paraphrase a famous quote, we can't improve what we don't have. Andrei
Aug 27 2012
parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Monday, August 27, 2012 14:54:52 Andrei Alexandrescu wrote:
 On 8/27/12 2:28 PM, Jonathan M Davis wrote:
 Except that the change which is causing Manu problems _isn't_ a new
 feature. It's a bug fix. So, better versioning wouldn't necessarily have
 helped him any at all.
Yah, this particular example is more of an example of the limitation of the stable/dev approach. Nevertheless, I do think we should switch to it.
Oh, I agree. I'm just saying that this is not a problem that a major.minor versioning system would solve. Such a versioning scheme could definitely be beneficial, but I think that many of those asking for it seriously overestimate how much it will reduce breaking changes. Aside from whatever deprecations occur (the number of which has been diminishing), they're almost exclusively caused by bug fixes.
 But more importantly we must first move to an integrally automated,
 scripted method of releasing software. The amount of manual drudgery
 that goes into today's release process makes my blood curd. To
 paraphrase a famous quote, we can't improve what we don't have.
Agreed. - Jonathan M Davis
Aug 27 2012
prev sibling next sibling parent Jacob Carlborg <doob me.com> writes:
On 2012-08-27 23:28, Jonathan M Davis wrote:

 Except that the change which is causing Manu problems _isn't_ a new feature.
 It's a bug fix. So, better versioning wouldn't necessarily have helped him any
 at all. At best, if we had a more complex versioning scheme, it could be
 decided that the bug fix was potentially disruptive enough that it should only
 be fixed in a more major release, but _every_ bug fix risks breaking code,
 especially when code could be relying on the buggy behavior.
The fix for this particular bug was to remove a language feature. I don't think it would be wise to put that change in a minor release. It doesn't matter why the feature was removed. If a feature is remove from the language it should only be put in a major release, period. -- /Jacob Carlborg
Aug 28 2012
prev sibling parent "foobar" <foo bar.com> writes:
On Monday, 27 August 2012 at 21:29:06 UTC, Jonathan M Davis wrote:
 On Monday, August 27, 2012 23:22:39 foobar wrote:
 All true, except one crucial fact: DMD gets critical bug fixes
 incorporated with new features in the same release. This 
 leaves a
 poor choice to the programmer, either he sticks with older
 compiler version and can't get any critical bug fixes, or he
 updates the compiler to latest version with all the bug fixes 
 but
 risks breakage of code due to new features (which is _exactly_
 what happened to manu).
Except that the change which is causing Manu problems _isn't_ a new feature. It's a bug fix. So, better versioning wouldn't necessarily have helped him any at all. At best, if we had a more complex versioning scheme, it could be decided that the bug fix was potentially disruptive enough that it should only be fixed in a more major release, but _every_ bug fix risks breaking code, especially when code could be relying on the buggy behavior. - Jonathan M Davis
I guess your definition of stable is different than mine. "Accepts invalid" bugs should never be fixed on the current stable branch as they introduce a breaking change unless it's a matter of a security problem. All compilers have a list of accepts invalid bugs and no one "fixes" them on the stable/release branch exactly because it will break existing code as evidently shown by manu's posts. Python introduced version 3 to handle this and Ruby had version 1.9, all the while both the stable and the next-version branches got bug-fixes. D was moved to Git long ago which trivially allows such branching schemes so there really no excuse to stick with the current flawed system except inertia.
Aug 28 2012
prev sibling parent reply Manu <turkeyman gmail.com> writes:
On 28 August 2012 00:08, Maxim Fomin <maxim maxim-fomin.ru> wrote:

 On Monday, 27 August 2012 at 20:29:15 UTC, deadalnix wrote:

 That guy just have to rework 5 projects. This is quite a lot of work.

 Obviously, that change have to be done. But you can't just impose such a
 change on users. Their agenda isn't synchronized with D's.

 D NEED a better versioning scheme, as explained MANY times here already.
I may be considered rude, but as I have understood from the first post Manu 1) decided to use a not fully designed language feature
Where is that stated? It appeared to be a perfectly good, working, and awesome(!) language feature. 2) used this feature in public API when working with DLLs (which is by
 itself a problematic area)
Are you suggesting using DLL's is asking for trouble? Dynamic linkage is a fundamental part of software. 3) integrated this with other language in a a way that require substantial
 rewrite and possibly redesign
D will interact with other languages in the real world, if anything, my doing so and talking about my approach and experience is a service. 4) updated compiler

No shit, D has loads of bugs which are rapidly fixed all the time.

which I consider a process of looking for problems and no excuse for making
 code-break change. Anyway, older version of compiler is available.
Whatever.
Aug 27 2012
parent Jacob Carlborg <doob me.com> writes:
On 2012-08-27 23:29, Manu wrote:

 Are you suggesting using DLL's is asking for trouble? Dynamic linkage is
 a fundamental part of software.
Yes, they don't properly work in D. -- /Jacob Carlborg
Aug 28 2012
prev sibling next sibling parent reply Jonathan M Davis <jmdavisProg gmx.com> writes:
On Monday, August 27, 2012 11:38:52 Manu wrote:
 On 27 August 2012 11:12, Jonathan M Davis <jmdavisProg gmx.com> wrote:
 and it makes no sense to use them with function pointers or function
 literals.
If that were true, we wouldn't be having this discussion.
You can't possibly really be using these functions with default arguments unless you're not really using them like function pointers, otherwise you wouldn't have been using the default arguments in the first place. Sure, you could have something like auto func = (string a = "hello") { return a; } and then call it with one argument or no argument, but that's only because the function is completely local, and you could just as easily do string func(string a = "hello") { return a; } and get the same thing. As soon as you've really used it as a function pointer rather than a local function, you've lost the default argument. Default arguments just do not make sense with function pointers, because they don't follow the function pointer, because it's a _pointer_ and has no knowledge of what it's pointing to. It's only at the declaration point of the function that the default argument exists, and that has _nothing_ to do with the function pointer. You might as well ask a reference of type Object what the arguments used to construct the derived class that it actually refers to were as expect a function pointer to have any clue about default arguments to the function that it points to. - Jonathan M Davis
Aug 27 2012
parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Jonathan M Davis" <jmdavisProg gmx.com> wrote in message 
news:mailman.1446.1346070222.31962.digitalmars-d puremagic.com...
 Default arguments just do not make sense with function pointers, because 
 they
 don't follow the function pointer, because it's a _pointer_ and has no
 knowledge of what it's pointing to. It's only at the declaration point of 
 the
 function that the default argument exists, and that has _nothing_ to do 
 with
 the function pointer. You might as well ask a reference of type Object 
 what
 the arguments used to construct the derived class that it actually refers 
 to
 were as expect a function pointer to have any clue about default arguments 
 to
 the function that it points to.

 - Jonathan M Davis
Yes, this. I looked into fixing issue 3866 earlier this year, and Kenji's list sounds very familiar. It comes down to - function pointers are not function declarations and therefore can't do everything function declarations do, such as overloading and default arguments. This is the price of being able to reassign them. Yes it is possible to make them work the way Manu has been using them, but the solution is messy and I seriously doubt it's worth it. I agree with everything Andrei has said about this. I don't consider putting default arguments back in the type a valid approach because of the reason above, so that leaves giving function pointer variables the ability to have default arguments. This seems to me like a really dumb corner case with the same problems.
Aug 28 2012
parent reply Manu <turkeyman gmail.com> writes:
On 28 August 2012 16:50, Daniel Murphy <yebblies nospamgmail.com> wrote:

 "Jonathan M Davis" <jmdavisProg gmx.com> wrote in message
 news:mailman.1446.1346070222.31962.digitalmars-d puremagic.com...
 Default arguments just do not make sense with function pointers, because
 they
 don't follow the function pointer, because it's a _pointer_ and has no
 knowledge of what it's pointing to. It's only at the declaration point of
 the
 function that the default argument exists, and that has _nothing_ to do
 with
 the function pointer. You might as well ask a reference of type Object
 what
 the arguments used to construct the derived class that it actually refers
 to
 were as expect a function pointer to have any clue about default
arguments
 to
 the function that it points to.

 - Jonathan M Davis
Yes, this. I looked into fixing issue 3866 earlier this year, and Kenji's list sounds very familiar. It comes down to - function pointers are not function declarations and therefore can't do everything function declarations do, such as overloading and default arguments. This is the price of being able to reassign them. Yes it is possible to make them work the way Manu has been using them, but the solution is messy and I seriously doubt it's worth it. I agree with everything Andrei has said about this. I don't consider putting default arguments back in the type a valid approach because of the reason above, so that leaves giving function pointer variables the ability to have default arguments. This seems to me like a really dumb corner case with the same problems.
Well that's painful for a number of reasons. Other than the fact that I need to rewrite a bunch of code, I can't actually produce a solution that works as well in lieu of some feature requests. A language feature should be deprecated for some time before it is surprise-removed like that... that would allow some time to discuss and possibly implement support for work-arounds. Since I'll need to create wrapper functions and stubs for *everything*, I really need __forceinline, or debug builds will really suffer. (That said, I need it anyway for heaps of other stuff (SIMD in particular), but this really seals the deal.) Without it, all function calls require call-through stubs, all my API calls become double-calls, and debugging becomes really tedious (stepping into and out of stubs generated by magic that don't really exist every time you press F11 >_<). Also, in my case, I really need to be able to forward declare a function in the same file as it is defined. The user needs to write the prototypes, and then magic can scan for the prototypes and generate the stub its self, but they can't currently appear in the same file like in C/C++. Without both of those things, my new solutions will be worse. More verbose, difficult to read&maintain, slower, and really annoying to debug. Why are you so against default arguments associating with a declaration? Why is the solution messy? Default arguments simplify code. If a default is used frequently, it's better to define it in one place, that is easy to change, and appears with the definition. It's self-documenting, and very convenient.
Aug 28 2012
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/28/12 8:23 AM, Manu wrote:
 Well that's painful for a number of reasons..
 Other than the fact that I need to rewrite a bunch of code,
Walter and Kenji think breaking meaningful existing code is an overriding concern, and I ended up agreeing with them. They will look into a solution that keeps your working code working. This change of wind may as well turn a new page in the history of D :o). Andrei
Aug 28 2012
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:

 This change of wind may as well turn a new page in the history 
 of D :o).
<humor>Maybe as soon as the D1 branch stops getting bug fixes, Walter&Co will start a D3 branch where this small breaking change happens.</humor> Bye, bearophile
Aug 28 2012
prev sibling next sibling parent "Carl Sturtivant" <sturtivant gmail.com> writes:
On Tuesday, 28 August 2012 at 18:52:13 UTC, Andrei Alexandrescu 
wrote:
 On 8/28/12 8:23 AM, Manu wrote:
 Well that's painful for a number of reasons..
 Other than the fact that I need to rewrite a bunch of code,
Walter and Kenji think breaking meaningful existing code is an overriding concern, and I ended up agreeing with them. They will look into a solution that keeps your working code working. This change of wind may as well turn a new page in the history of D :o).
I replied to Walter just now with a way to get a clean mechanism to do this, that's consistent with the existing type system, and with function pointers working exactly as they do now. Carl.
Aug 28 2012
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
On 28 August 2012 21:52, Andrei Alexandrescu
<SeeWebsiteForEmail erdani.org>wrote:

 On 8/28/12 8:23 AM, Manu wrote:

 Well that's painful for a number of reasons..

 Other than the fact that I need to rewrite a bunch of code,
Walter and Kenji think breaking meaningful existing code is an overriding concern, and I ended up agreeing with them. They will look into a solution that keeps your working code working. This change of wind may as well turn a new page in the history of D :o).
Wow, I didn't see that coming. At very least, just put it on a deprecation schedule. I'm happy (perhaps even prefer) to use the alternative approach I've describe if those 2 issues are addressed in some way.
Aug 28 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, August 29, 2012 01:13:15 Manu wrote:
 On 28 August 2012 21:52, Andrei Alexandrescu
 
 <SeeWebsiteForEmail erdani.org>wrote:
 On 8/28/12 8:23 AM, Manu wrote:
 Well that's painful for a number of reasons..
 
 Other than the fact that I need to rewrite a bunch of code,
Walter and Kenji think breaking meaningful existing code is an overriding concern, and I ended up agreeing with them. They will look into a solution that keeps your working code working. This change of wind may as well turn a new page in the history of D :o).
Wow, I didn't see that coming. At very least, just put it on a deprecation schedule. I'm happy (perhaps even prefer) to use the alternative approach I've describe if those 2 issues are addressed in some way.
The funny thing about that is that for the most part, language features which are supposed to be deprecated tend to just stick around instead of getting deprecated, meaning that people keep on using them, and that by the time they're actually deprecated, they'll break that much more code... It's one thing to decide not to make a change becasue we don't want to break code. It's quite another to just keep putting it off to avoid breaking code. That just makes things worse when it finally happens. - Jonathan M Davis
Aug 28 2012
prev sibling next sibling parent Manu <turkeyman gmail.com> writes:
On 29 August 2012 04:31, Jonathan M Davis <jmdavisProg gmx.com> wrote:

 On Wednesday, August 29, 2012 01:13:15 Manu wrote:
 On 28 August 2012 21:52, Andrei Alexandrescu

 <SeeWebsiteForEmail erdani.org>wrote:
 On 8/28/12 8:23 AM, Manu wrote:
 Well that's painful for a number of reasons..

 Other than the fact that I need to rewrite a bunch of code,
Walter and Kenji think breaking meaningful existing code is an
overriding
 concern, and I ended up agreeing with them.

 They will look into a solution that keeps your working code working.

 This change of wind may as well turn a new page in the history of D
:o).
 Wow, I didn't see that coming.

 At very least, just put it on a deprecation schedule. I'm happy (perhaps
 even prefer) to use the alternative approach I've describe if those 2
 issues are addressed in some way.
The funny thing about that is that for the most part, language features which are supposed to be deprecated tend to just stick around instead of getting deprecated, meaning that people keep on using them, and that by the time they're actually deprecated, they'll break that much more code... It's one thing to decide not to make a change becasue we don't want to break code. It's quite another to just keep putting it off to avoid breaking code. That just makes things worse when it finally happens.
I've conceded that I don't mind changing my code, if a satisfactory alternative exists (it doesn't currently). Also, I'd like to have some notice that I need to make some time to change the code, and the opportunity to work it into my schedule. Surely that's not unreasonable.
Aug 29 2012
prev sibling next sibling parent reply kenji hara <k.hara.pg gmail.com> writes:
OK. I have finished working to implement it as a new pull request.
https://github.com/D-Programming-Language/dmd/pull/1102

It contains several test suites that indicate which case is supported or not.
https://github.com/D-Programming-Language/dmd/pull/1102/files#L6R1

Regards.

Kenji Hara

2012/8/29 Andrei Alexandrescu <SeeWebsiteForEmail erdani.org>:
 On 8/28/12 8:23 AM, Manu wrote:
 Well that's painful for a number of reasons..

 Other than the fact that I need to rewrite a bunch of code,
Walter and Kenji think breaking meaningful existing code is an overriding concern, and I ended up agreeing with them. They will look into a solution that keeps your working code working. This change of wind may as well turn a new page in the history of D :o). Andrei
Sep 06 2012
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 9/6/12 11:15 AM, kenji hara wrote:
 OK. I have finished working to implement it as a new pull request.
 https://github.com/D-Programming-Language/dmd/pull/1102

 It contains several test suites that indicate which case is supported or not.
 https://github.com/D-Programming-Language/dmd/pull/1102/files#L6R1

 Regards.

 Kenji Hara
Thank you very much! Andrei
Sep 06 2012
prev sibling parent Manu <turkeyman gmail.com> writes:
On 6 September 2012 12:15, kenji hara <k.hara.pg gmail.com> wrote:

 OK. I have finished working to implement it as a new pull request.
 https://github.com/D-Programming-Language/dmd/pull/1102

 It contains several test suites that indicate which case is supported or
 not.
 https://github.com/D-Programming-Language/dmd/pull/1102/files#L6R1
Awesome, thanks for this! How far off is 2.61 likely to be?
Sep 12 2012
prev sibling next sibling parent reply Manu <turkeyman gmail.com> writes:
On 27 August 2012 13:54, Jonathan M Davis <jmdavisProg gmx.com> wrote:

 On Monday, August 27, 2012 11:38:52 Manu wrote:
 On 27 August 2012 11:12, Jonathan M Davis <jmdavisProg gmx.com> wrote:
 and it makes no sense to use them with function pointers or function
 literals.
If that were true, we wouldn't be having this discussion.
You can't possibly really be using these functions with default arguments unless you're not really using them like function pointers, otherwise you wouldn't have been using the default arguments in the first place.
How do I use a function pointer 'like a function pointer' by your reasoning? Is dynamic linkage not a valid use of function pointers? It's possibly the *most* common use. Shared code is not exactly uncommon, just that it's often automated with compiler sugar these days. Manually binding dynamic code has become rare, but it's still valid. Default arguments just do not make sense with function pointers, because
 they
 don't follow the function pointer, because it's a _pointer_ and has no
 knowledge of what it's pointing to. It's only at the declaration point of
 the
 function that the default argument exists, and that has _nothing_ to do
 with
 the function pointer.
Clearly, since it's a function pointer, I don't own the function its self, or I'd just call that directly. However, as you say, at the declaration point of the function pointer, I can likewise inform the compiler of the default args. If the default args were worked into the variable declaration, that might work just as well as if it were in the type. I don't really care so much HOW it works, only that it does. I think 99% of my cases would be addressed by the def args living in the variable declaration. You might as well ask a reference of type Object what
 the arguments used to construct the derived class that it actually refers
 to
 were as expect a function pointer to have any clue about default arguments
 to
 the function that it points to.
I agree, in principle, but I've still lost a thoroughly useful feature. Move it perhaps (to the declaration?), but don't *re-*move it (cough).
Aug 27 2012
parent Walter Bright <newshound2 digitalmars.com> writes:
On 8/27/2012 5:38 AM, Manu wrote:
 I don't really care so much HOW it works, only that it does. I think 99% of my
 cases would be addressed by the def args living in the variable declaration.
Can you please post a canonical 99% use case that you use? I cannot suggest an alternative otherwise. I need to understand what coding problem you have been solving with default args. For example, the basic use case of default args is just a shorthand for doing function overloads: int foo(int a = 5) { ... } becomes: int foo(int a) { ... } int foo() { return foo(5); } Many use cases involving templates can be resolved by using alias template parameters. But I don't know what your use cases are.
Aug 27 2012
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 26 Aug 2012 18:26:40 -0400, Manu <turkeyman gmail.com> wrote:

 I just updated to 2.60 and found errors throughout my code where function
 pointers default args no longer work.
 *Every single project* I've written in D, 5 projects, don't work anymore,
 including my projects at work.
OK, I've read all the posts on this, and this is what I think would solve the problem: 1. default parameters are part of the function pointer type, because a function pointer has a type. 2. The mangled name of the function that is assigned to that function pointer does *not* have default parameters mangled in. Only the function pointer type has them. 3. Since the default parameters are part of the type, but not defined by the function it points to, you can use interchangeably functions of the same type which define default parameters or not (or define different ones). The default parameters follow the function pointer variable. 4. If a template instantiates with a function type that takes a default parameter, that default parameter is mangled as part of the template type. There is no way around this. But the mangling of the default parameter is *separate* from the mangling of the function type. We can probably implement this as a library feature, but I lack the full template-fu skills to do it. It would be nice to have the compiler do this for us, but I don't know the work involved. Here is a pseudocode type we may be able to use: struct DefaultParamFuncPtr(F, alias T...) if(is(F == function) && isValues!T) // no idea how to define isValues { F funcptr; static T defaultparams; alias funcptr this; // allow this type to act as a straight F, or accept an F to rebind to a different function typeof(funcptr()) opCall(X...)(X params) if(X is a prefix of F's args) // no idea how to write this constraint :) { // construct the rest of the tuple for params given defaultparams typeof(F's args) realtuple = params ~ defaultparams[params.length..$]; // construct the full call parameters return funcptr(realtuple); } } Yeah, it would be really nice to do this in the compiler, but I think the above describes what I would consider to be an equivalent type. The default parameters live as metadata in the type of the function pointer, but are separate from the function type that it points to. -Steve
Aug 27 2012
parent reply Don Clugston <dac nospam.com> writes:
On 27/08/12 16:16, Steven Schveighoffer wrote:
 On Sun, 26 Aug 2012 18:26:40 -0400, Manu <turkeyman gmail.com> wrote:

 I just updated to 2.60 and found errors throughout my code where function
 pointers default args no longer work.
 *Every single project* I've written in D, 5 projects, don't work anymore,
 including my projects at work.
OK, I've read all the posts on this, and this is what I think would solve the problem: 1. default parameters are part of the function pointer type, because a function pointer has a type. 2. The mangled name of the function that is assigned to that function pointer does *not* have default parameters mangled in. Only the function pointer type has them. 3. Since the default parameters are part of the type, but not defined by the function it points to, you can use interchangeably functions of the same type which define default parameters or not (or define different ones). The default parameters follow the function pointer variable.
This sounds like sloppy thinking. I _think_ what you are saying is that there should be implicit conversions from one function pointer type, to any other that has the same basic declaration, but different default arguments. But then you get problems with IFTI. void foo( void function(int x), double) {} void foo( void function(int x), int) {} void function(int x = 10) bar; foo(bar, 5); // fails -- ambiguous. Both overloads match with implicit conversions. But really, it seems to me that this whole feature is just syntax sugar for one special case of currying.
Aug 28 2012
parent "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 28 Aug 2012 04:57:53 -0400, Don Clugston <dac nospam.com> wrote:

 On 27/08/12 16:16, Steven Schveighoffer wrote:
 On Sun, 26 Aug 2012 18:26:40 -0400, Manu <turkeyman gmail.com> wrote:

 I just updated to 2.60 and found errors throughout my code where  
 function
 pointers default args no longer work.
 *Every single project* I've written in D, 5 projects, don't work  
 anymore,
 including my projects at work.
OK, I've read all the posts on this, and this is what I think would solve the problem: 1. default parameters are part of the function pointer type, because a function pointer has a type. 2. The mangled name of the function that is assigned to that function pointer does *not* have default parameters mangled in. Only the function pointer type has them. 3. Since the default parameters are part of the type, but not defined by the function it points to, you can use interchangeably functions of the same type which define default parameters or not (or define different ones). The default parameters follow the function pointer variable.
This sounds like sloppy thinking. I _think_ what you are saying is that there should be implicit conversions from one function pointer type, to any other that has the same basic declaration, but different default arguments. But then you get problems with IFTI. void foo( void function(int x), double) {} void foo( void function(int x), int) {} void function(int x = 10) bar; foo(bar, 5); // fails -- ambiguous. Both overloads match with implicit conversions.
First, I don't see IFTI anywhere in there. Second, why is this a problem? We already have oddities in terms of how implicitly converting one argument makes the interpretation of non-ambiguous arguments ambiguous. I don't think it's sloppy, my logic is: we can already (I think) do this in library code, so why is it impossible for the compiler to figure out?
 But really, it seems to me that this whole feature is just syntax sugar  
 for one special case of currying.
Probably. But it is what it is -- a poorly implemented/designed language feature. In most cases, we don't simply drop them without giving a replacement. Examples: octal literals, complex numbers, scope classes. -Steve
Aug 29 2012
prev sibling next sibling parent "Maxim Fomin" <maxim maxim-fomin.ru> writes:
I think that D should be consistent with rules of types 
derivation, in particular the pointer one. Two pointer types 
should be same if and only if types they pointer are the same. 
Breaking this rule would result in language complexity.

In case when default arguments are part of the type, default 
arguments must be written in declaration of a function pointer, 
moreover function pointers with different default arguments would 
be different types. So, besides of getting convenient usage of a 
shorter function call a user would have carry a particular 
default value with every declaration/definition he writes. Also 
similar functions pointers with different default arguments would 
not be interchangeable because they are different types. I think 
that such constraints would make the proposal a net loss to D 
contribution.

Also, making default arguments a part of a definition would break 
C/C++ legacy rules which claim that a function is characterized 
by its return value and number, order and types of its 
parameters. Although, it not necessarily mean that this rule 
cannot be broken, but it at least requires strong reasons, 
however as it is discussed, the proposal would result in a 
unclear usage, increased language complexity, subtle bugs and 
implementation issues.
Aug 27 2012
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Wednesday, August 29, 2012 12:40:45 Manu wrote:
 I've conceded that I don't mind changing my code, if a satisfactory
 alternative exists (it doesn't currently). Also, I'd like to have some
 notice that I need to make some time to change the code, and the
 opportunity to work it into my schedule.
 Surely that's not unreasonable.
Ideally, something which is going to be deprecated is first scheduled for deprecation (and marked as such in both the changelog and documentation) and then later deprecated, giving programmer's time to change their code first. It almost always works that way with druntime and Phobos, but I don't think that we necessarily do as good a job with the compiler. Frequently the situation is that people on the newsgroup know fullwell that a feature is going to be deprecated (e.g. delete), but that's not well communicated outside of this group, and for some reason, the to-be-deprecated items just don't get deprecated even though it's been definitively decided that they will be at some point. In this particular case, it was viewed as a bug fix, and bug fixes break code all the time (especially accepts-invalid bugs), so no effort was made to indicate that a feature was going away (i.e. it was viewed as feature being fixed rather than a feature going away, so no notification beyond the bugfix in the changelog was given). Obviously, this was not handled in the best way, as this thread has shown. But stuff that's actually viewed as being deprecated generally is handled much better. - Jonathan M Davis
Aug 29 2012