August 30, 2012
...although, now I'm thinking that having reference variables as members of struct or class won't ever work. And that's because T.init is a compile-time variable, and references can't be known at compile-time (unlike pointers, which can be null). Perhaps the easiest way to implement reference variables would be to just think about them as syntactic sugar, and use lowering. Something like this user code...

void main()
{
    int           var = 123;
    immutable int imm = 42;

    ref int rVar = var;
    rVar = 1234;

    ref immutable int rImm = imm;
    auto mult = rImm * rImm;
}

// ...would get lowered to:

void main()
{
    int           var = 123;
    immutable int imm = 42;

    int* pVar = &var;
    (*pVar) = 1234;

    immutable(int)* pImm = &imm;
    auto mult = (*pImm) * (*pImm);
}

And this kind of initialization would be always illegal:
ref int rVar;
August 30, 2012
I think references should either take the C# approach (which is rock-solid), or the C++ approach (which is also pretty rock-solid), but not an in-between.
August 30, 2012
> I had totally forgotten what it says in "The book" about struct and class construction. It's basically that all fields are first initialized to either T.init or by using the field's initializer. That means the use of ref inside class or struct would be quite restricted:
>
> int globalVal;
>
> struct MyStruct
> {
>     // ref int defaultInitRef; // Illegal: reference variables
>                                // can't be default initialized

But you can handle it like const members: you have to initialize these members in the ctor.
August 30, 2012
On Thursday, 30 August 2012 at 07:35:34 UTC, Namespace wrote:
>
>> I had totally forgotten what it says in "The book" about struct and class construction. It's basically that all fields are first initialized to either T.init or by using the field's initializer. That means the use of ref inside class or struct would be quite restricted:
>>
>> int globalVal;
>>
>> struct MyStruct
>> {
>>    // ref int defaultInitRef; // Illegal: reference variables
>>                               // can't be default initialized
>
> But you can handle it like const members: you have to initialize these members in the ctor.

Furthermore I suggest that with "ref" marked Objects _can't_ be null.
So ref Foo fr = null; is equally forbidden as
[code]
Foo f; // same as Foo f = null;
ref Foo fr = f;
[/code]
Why? First: null isn't an lvalue and even if "Foo f;" is one: if it would be allowed, you have a useless reference, because it is null and you can't assign a valid object to it (because you can assign ref's just once)
Furthermore it solve the problem which I often annotate: not null paramters.
void do_something(ref Foo fr) { <- "fr" can't be null, you can trust it without any other validations.
I would love that. :)
August 30, 2012
On Thursday, 30 August 2012 at 07:35:34 UTC, Namespace wrote:
>
>> struct MyStruct
>> {
>>    // ref int defaultInitRef; // Illegal: reference variables
>>                               // can't be default initialized
>
> But you can handle it like const members: you have to initialize these members in the ctor.

Yeah, maybe. I'm starting to think we should really know the implementation details of the language in order to even speculate about how ref might be implemented.

On Thursday, 30 August 2012 at 07:35:34 UTC, Namespace wrote:
> So ref Foo fr = null; is equally forbidden as
> [code]
> Foo f; // same as Foo f = null;
> ref Foo fr = f;

But you can always say:

Foo f = new Foo();
ref Foo fr = f;
f = null;

...after which fr references f, which references null, so effectively fr references null. I don't think this could be prevented from happening.

August 30, 2012
On Thursday, 30 August 2012 at 08:57:24 UTC, Tommi wrote:
> On Thursday, 30 August 2012 at 07:35:34 UTC, Namespace wrote:
>>
>>> struct MyStruct
>>> {
>>>   // ref int defaultInitRef; // Illegal: reference variables
>>>                              // can't be default initialized
>>
>> But you can handle it like const members: you have to initialize these members in the ctor.
>
> Yeah, maybe. I'm starting to think we should really know the implementation details of the language in order to even speculate about how ref might be implemented.
I agree.

> On Thursday, 30 August 2012 at 07:35:34 UTC, Namespace wrote:
>> So ref Foo fr = null; is equally forbidden as
>> [code]
>> Foo f; // same as Foo f = null;
>> ref Foo fr = f;
>
> But you can always say:
>
> Foo f = new Foo();
> ref Foo fr = f;
> f = null;
>
> ...after which fr references f, which references null, so effectively fr references null. I don't think this could be prevented from happening.

That's true... Maybe in "ref Foo fr = f;" fr shouln't be a real pointer to f and instead an object which refer to the concrete object of f. Then it would be indifferent if you write f = null; because "fr" refers still to a valid object.

August 30, 2012
Tommi:

>     // This prints 1, so we called the actual method

I think in D methods have precedence over free functions.

Bye,
bearophile
August 30, 2012
On Thursday, 30 August 2012 at 11:53:14 UTC, bearophile wrote:
> Tommi:
>
>>    // This prints 1, so we called the actual method
>
> I think in D methods have precedence over free functions.
>
> Bye,
> bearophile

Yes, but to me the ambiguity of that example is in whether or not implicit deferencing of pointers has precedence over uniform function call syntax. Apparently it does, but it's not that obvious that it would.

struct MyStruct
{
    int _value = 0;

    void increment()
    {
        ++_value;
    }
}

void increment(ref MyStruct* ptr)
{
    ++ptr;
}

void main()
{
    MyStruct* ptrMyStruct = new MyStruct();

    // Are we incrementing the pointer using UFCS or
    // are we calling the member function in MyStruct?
    ptrMyStruct.increment();
}
August 30, 2012
On Thursday, August 30, 2012 15:30:15 Tommi wrote:
> Yes, but to me the ambiguity of that example is in whether or not implicit deferencing of pointers has precedence over uniform function call syntax. Apparently it does, but it's not that obvious that it would.

It _can't_ work any other way, because there's no way to tell the compiler to use the member function specifically. You can use the full import path for the free function, so you can tell the compiler to use the free function. There's no such syntax for member variables.

> struct MyStruct
> {
> int _value = 0;
> 
> void increment()
> {
> ++_value;
> }
> }
> 
> void increment(ref MyStruct* ptr)
> {
> ++ptr;
> }
> 
> void main()
> {
> MyStruct* ptrMyStruct = new MyStruct();
> 
> // Are we incrementing the pointer using UFCS or
> // are we calling the member function in MyStruct?
> ptrMyStruct.increment();
> }

For instance, if you do

increment(ptrMyStruct);

or

.increment(ptrMyStruct);

or if increment had a longer import path

path.to.increment(ptrMyStruct);

then you can tell the compiler to use the free function. But how would you do that with the member function? You can't. So, there's really no other choice but to choose the member function whenever there's a conflict. It also prevents function hijacking so that something like var.increment() doesn't suddenly start using using a free function instead of the member function when you add an import which has an increment free function.

- Jonathan M Davis
August 31, 2012
On Thursday, 30 August 2012 at 17:50:47 UTC, Jonathan M Davis wrote:
> On Thursday, August 30, 2012 15:30:15 Tommi wrote:
>> Yes, but to me the ambiguity of that example is in whether or not
>> implicit deferencing of pointers has precedence over uniform
>> function call syntax. Apparently it does, but it's not that
>> obvious that it would.
>
> It _can't_ work any other way, because there's no way to tell the compiler to
> use the member function specifically. You can use the full import path for the
> free function, so you can tell the compiler to use the free function. There's
> no such syntax for member variables.
>
>> struct MyStruct
>> {
>> int _value = 0;
>> 
>> void increment()
>> {
>> ++_value;
>> }
>> }
>> 
>> void increment(ref MyStruct* ptr)
>> {
>> ++ptr;
>> }
>> 
>> void main()
>> {
>> MyStruct* ptrMyStruct = new MyStruct();
>> 
>> // Are we incrementing the pointer using UFCS or
>> // are we calling the member function in MyStruct?
>> ptrMyStruct.increment();
>> }
>
> For instance, if you do
>
> increment(ptrMyStruct);
>
> or
>
> .increment(ptrMyStruct);
>
> or if increment had a longer import path
>
> path.to.increment(ptrMyStruct);
>
> then you can tell the compiler to use the free function. But how would you do
> that with the member function? You can't. So, there's really no other choice
> but to choose the member function whenever there's a conflict. It also prevents
> function hijacking so that something like var.increment() doesn't suddenly
> start using using a free function instead of the member function when you add
> an import which has an increment free function.
>
> - Jonathan M Davis

But this is not about member function vs. free function. This is about implicit pointer dereferencing vs. UFCS. The question is: "what does member access operator do, when it operates on a pointer?". There are two options, and I think they are both valid options the language could have chosen:

// S is a struct:
auto ptr = new S();

// What to do with this?
ptr.fun(); // or ptr.fun;

Option #1:
  Rewrite the expression as (*ptr).fun()

Option #2:
  If .fun(S*) exists: call the free function .fun(ptr)
  Else: rewrite the expression as (*ptr).fun()

...But, actually it seems that the language is a bit broken, because it doesn't follow neither one of those two options. What it actually does is this:

Option #3:
  If S.init.fun() exists: call the member (*ptr).fun()
  Else if .fun(S*) exists: call the free function .fun(ptr)
  Else give a compile time error

Here's the details:

struct S1
{
    int _value = 42;

    void fun()
    {
        ++_value;
    }
}

void fun(ref S1 s1)
{
    s1._value += 1000;
}

void fun(ref S1* ptr1)
{
    ++ptr1;
}
/////////////////////////////
struct S2
{
    int _value = 42;
}

void fun(ref S2 s2)
{
    ++s2._value;
}

void fun(ref S2* ptr2)
{
    ++ptr2;
}
/////////////////////////////
struct S3
{
    int _value = 42;
}

void fun(ref S3 s3)
{
    ++s3._value;
}


void main()
{
    auto ptr1 = (new S1[2]).ptr;
    ptr1.fun();            // calls (*ptr1).fun()
    writeln(ptr1._value);  // prints 43

    auto arr2 = new S2[2];
    auto ptr2 = arr2.ptr;
    arr2[1]._value = 12345;
    ptr2.fun();            // calls .fun(ptr2)
    writeln(ptr2._value);  // prints 12345

    auto ptr3 = (new S3[2]).ptr;
    ptr3.fun(); // Error: function main.fun (ref S3 s3) is
                // not callable using argument types (S3*)
}

I think, if we want to have implicit pointer dereferencing (I'm not even sure it's a good thing), then option #1 would be the best choice.
1 2 3 4 5
Top | Discussion index | About this forum | D home