View mode: basic / threaded / horizontal-split · Log in · Help
August 30, 2012
Re: Why can't we make reference variables?
...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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
> 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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
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
Re: Why can't we make reference variables?
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