August 27, 2012
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
August 27, 2012
>
> 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.


August 27, 2012
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.

August 27, 2012
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;
}


August 27, 2012
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
August 27, 2012
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!



August 27, 2012
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!

August 27, 2012
>
> 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?

August 27, 2012
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
August 27, 2012
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.