March 25, 2018
On Sunday, 25 March 2018 at 01:43:43 UTC, Jonathan M Davis wrote:
> On Sunday, March 25, 2018 00:34:38 Rubn via Digitalmars-d wrote:
>> On Saturday, 24 March 2018 at 23:03:36 UTC, John Colvin wrote:
>> > Auto ref allows the unnecessary copy to be avoided for lvalues and creates a temporary (as part of passing the value) for rvalues. It has downsides (virtual functions and extern(C++), but it does directly address the problem you're talking about, unless I have totally misunderstood you.
>>
>> You are trading syntax bloat for binary bloat. Having 4 parameters with auto ref can generate 16 different variants for the exact same function. If you are doing any sort of mathematical calculations you could cause a potential cache miss for calling the same exact function because one of your parameters didn't end up being the same type of value. You are causing all this bloat and potential slowdowns all to avoid having to do this:
>>
>> float value0 = a + b;
>> float value1 = c + d;
>> float value2 = e + f;
>> someFunc(value0, value1, value2);
>>
>> That's an inherent flaw in design. You obviously agree that there is a problem, but how can you justify "auto ref" being a "good" solution to the problem? It's a horrible one, it causes excessive bloat and potential slowdowns just to get a cleaner readable syntax. That shouldn't be a solution that is deemed acceptable.
>
> How good or bad it is depends on what you're doing and how many auto ref parameters there are in your code. If you're using it occasionally, it's not a problem at all, whereas if you're using it all over the place, you do get a lot of template bloat.
>
> Regardless, John's point was that auto ref solves the problem of being able to call a function with both lvalues and rvalues without copying lvalues, and he didn't understand why anyone was trying to argue that it doesn't do that. And I agree with him on that point. It does not help with virtual functions or extern(C++), and it creates a lot of template bloat if it's used heavily, so there are downsides to it, but it _does_ solve the basic problem of being able to call a function with both lvalues and rvalues without copying the lvalues. It just doesn't solve it in a way that everyone considers acceptable.

That's a horrible way to look at it. You can still technically drive a car with square wheels, it may not be a very comfortable or efficient ride, but it still __does__ solve the basic problem of moving the car. Because it works we shouldn't look at any other solution.

> auto ref also helps with forwarding refness, so it's useful for more than just avoiding copying lvalues, but the entire reason that auto ref was originally added to the language was to solve this exact problem. And maybe we need a different solution for some use cases (like virtual functions or cases where the template bloat is deemed unacceptable), but auto ref is in the language to solve this problem. So, much as at may make sense to argue that it's not a good solution to this problem, it really doesn't make sense to argue that it has nothing to do with it.

I never said it has nothing to do with it. It's just a really horrible solution.

If you are going to add an exception just for two minor cases, at that point you might as well start looking at adding rvalue references, cause that's what it is going to lead to. It'd provided better integration with C++ and provide an __acceptable__ solution to the problem (not one that solves the problem in a roundabout square-wheel-like way) that doesn't create runtime bloat and slowdown just to have clean readable syntax.


March 26, 2018
On Friday, 23 March 2018 at 22:01:44 UTC, Manu wrote:
> Forked from the x^^y thread...
> <snip>

There are too many replies on this thread, addressing all the comments would take forever and pollute the thread itself. So forgive me if I say something that was covered already by someone else.

AFAIK being able to bind rvalues to `ref const(T)`, only makes sense when calling C++ functions that take `const T&` (especially since that is common). I have not yet heard any other use for them. I'd be in favour of allowing it _only_ for `extern(C++)` functions. Otherwise use `auto ref` or have overloads for pass-by-value and pass-by-ref.
I too, once a recent immigrant from the lands of C++, used to keep writing `ref const(T)`. I just pass by value now.

C++ T&& (forwarding reference) -> D auto ref T
C++ T&& (Rvalue reference) -> D T
C++ const T& -> D T
C++ T& -> D ref T

If replacing const T& with T chafes, I understand. I used to feel that way too. It's _possible_ that would incur a penalty in copying/moving, but IME the cost is either 0, negligible, or negative (!).

As mentioned above, if calling C++ code there's no choice about using T instead of const T&, so for pragmatic reasons that should be allowed. But only there, because...

> Can you please explain these 'weirdities'?
> What are said "major unintended consequences"?

Rvalue references. They exist because of being able to bind temporaries to const T& in C++, which means there's no way of knowing if your const T& was originally a temporary or not. To disambiguate C++11 introduced the type system horror that are rvalue references (which also do double-duty in enabling perfect forwarding!).

D doesn't have or need rvalue references _because_ of not allowing temporaries to bind to ref const(T). You get move semantics in D without the pain. That's a win in my book.

Atila

March 26, 2018
On Monday, 26 March 2018 at 14:40:03 UTC, Atila Neves wrote:
> On Friday, 23 March 2018 at 22:01:44 UTC, Manu wrote:
>> Forked from the x^^y thread...
>> <snip>
>
> There are too many replies on this thread, addressing all the comments would take forever and pollute the thread itself. So forgive me if I say something that was covered already by someone else.
>
> AFAIK being able to bind rvalues to `ref const(T)`, only makes sense when calling C++ functions that take `const T&` (especially since that is common). I have not yet heard any other use for them. I'd be in favour of allowing it _only_ for `extern(C++)` functions. Otherwise use `auto ref` or have overloads for pass-by-value and pass-by-ref.
> I too, once a recent immigrant from the lands of C++, used to keep writing `ref const(T)`. I just pass by value now.
>
> C++ T&& (forwarding reference) -> D auto ref T
> C++ T&& (Rvalue reference) -> D T
> C++ const T& -> D T
> C++ T& -> D ref T
>
> If replacing const T& with T chafes, I understand. I used to feel that way too. It's _possible_ that would incur a penalty in copying/moving, but IME the cost is either 0, negligible, or negative (!).

I'm tearing my remaining stubs of hair out trying to understand why memory copies (not talking about copy constructors) are needed when passing an rvalue to a non-ref function: https://stackoverflow.com/questions/49474685/passing-rvalue-to-non-ref-parameter-why-cant-the-compiler-elide-the-copy
March 26, 2018
On 26 March 2018 at 07:40, Atila Neves via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On Friday, 23 March 2018 at 22:01:44 UTC, Manu wrote:
>>
>> Forked from the x^^y thread...
>> <snip>
>
>
> There are too many replies on this thread, addressing all the comments would take forever and pollute the thread itself. So forgive me if I say something that was covered already by someone else.
>
> AFAIK being able to bind rvalues to `ref const(T)`, only makes sense when
> calling C++ functions that take `const T&` (especially since that is
> common). I have not yet heard any other use for them. I'd be in favour of
> allowing it _only_ for `extern(C++)` functions. Otherwise use `auto ref` or
> have overloads for pass-by-value and pass-by-ref.
> I too, once a recent immigrant from the lands of C++, used to keep writing
> `ref const(T)`. I just pass by value now.
>
> C++ T&& (forwarding reference) -> D auto ref T
> C++ T&& (Rvalue reference) -> D T
> C++ const T& -> D T

Yeah, no... T may be big. Copying a large thing sucks. Memory copying
is the slowest thing computers can do.
As an API author, exactly as in C++, you will make a judgement on a
case-by-case basis on this matter. It may be by-value, it may be by
const-ref. It depends on a bunch of things, and they are points for
consideration by the API author, not the user.

> C++ T& -> D ref T

I agree the other 3 cases are correct.
March 26, 2018
On 26 March 2018 at 11:13, John Colvin via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On Monday, 26 March 2018 at 14:40:03 UTC, Atila Neves wrote:
>>
>> On Friday, 23 March 2018 at 22:01:44 UTC, Manu wrote:
>>>
>>> Forked from the x^^y thread...
>>> <snip>
>>
>>
>> There are too many replies on this thread, addressing all the comments would take forever and pollute the thread itself. So forgive me if I say something that was covered already by someone else.
>>
>> AFAIK being able to bind rvalues to `ref const(T)`, only makes sense when
>> calling C++ functions that take `const T&` (especially since that is
>> common). I have not yet heard any other use for them. I'd be in favour of
>> allowing it _only_ for `extern(C++)` functions. Otherwise use `auto ref` or
>> have overloads for pass-by-value and pass-by-ref.
>> I too, once a recent immigrant from the lands of C++, used to keep writing
>> `ref const(T)`. I just pass by value now.
>>
>> C++ T&& (forwarding reference) -> D auto ref T
>> C++ T&& (Rvalue reference) -> D T
>> C++ const T& -> D T
>> C++ T& -> D ref T
>>
>> If replacing const T& with T chafes, I understand. I used to feel that way too. It's _possible_ that would incur a penalty in copying/moving, but IME the cost is either 0, negligible, or negative (!).
>
>
> I'm tearing my remaining stubs of hair out trying to understand why memory
> copies (not talking about copy constructors) are needed when passing an
> rvalue to a non-ref function:
> https://stackoverflow.com/questions/49474685/passing-rvalue-to-non-ref-parameter-why-cant-the-compiler-elide-the-copy

Passing rvalues to non-ref functions may elide a memory copy. Moves
can be very efficient.
But we're talking about ref functions right? Not not-ref functions...?
March 26, 2018
On Friday, 23 March 2018 at 22:01:44 UTC, Manu wrote:
>
> Can you please explain these 'weirdities'?
> What are said "major unintended consequences"?
> Explain how the situation if implemented would be any different than
> the workaround?
>
> This seems even simpler than the pow thing to me.
> Rewrite:
>     func(f());
> as:
>     { auto __t0 = f(); func(__t0); }
>
>
> How is that worse than the code you have to write:
>     T temp = f();
>     T zero = 0;
>     func(temp, zero);
>

I feel like this example wasn't really concrete enough for me. I wrote a version below that I think made it a little clearer for myself.

-----

import std.stdio : writeln;

struct Foo
{
    int data;
}

int foo(Foo x)
{
    writeln("here");
    return x.data;
}

int foo(ref Foo x)
{
    writeln("there");
    return x.data;
}


void main()
{
    auto x = Foo(5);
    auto y = foo(x);
    writeln(y);

    auto z = foo(Foo(5));
    writeln(z);
}
March 26, 2018
On 3/26/2018 12:24 PM, Manu wrote:
> On 26 March 2018 at 07:40, Atila Neves via Digitalmars-d
>> C++ const T& -> D T
> 
> Yeah, no... T may be big. Copying a large thing sucks. Memory copying
> is the slowest thing computers can do.
> As an API author, exactly as in C++, you will make a judgement on a
> case-by-case basis on this matter. It may be by-value, it may be by
> const-ref. It depends on a bunch of things, and they are points for
> consideration by the API author, not the user.

Copying does suck, I agree. Consider the following:

    void foo(T t) { foo(t); } <= add this overload
    void foo(ref T t) { ... }
    T aaa();

    foo(aaa());

With inlining, I suspect we can get the compiler to not make any extra copies. It's not that different from NRVO. And as a marvy bonus, no weird semantic problems (as Atila mentioned).

March 26, 2018
On Monday, 26 March 2018 at 22:48:38 UTC, Walter Bright wrote:
> On 3/26/2018 12:24 PM, Manu wrote:
>> On 26 March 2018 at 07:40, Atila Neves via Digitalmars-d
>>> C++ const T& -> D T
>> 
>> Yeah, no... T may be big. Copying a large thing sucks. Memory copying
>> is the slowest thing computers can do.
>> As an API author, exactly as in C++, you will make a judgement on a
>> case-by-case basis on this matter. It may be by-value, it may be by
>> const-ref. It depends on a bunch of things, and they are points for
>> consideration by the API author, not the user.
>
> Copying does suck, I agree. Consider the following:
>
>     void foo(T t) { foo(t); } <= add this overload
>     void foo(ref T t) { ... }
>     T aaa();
>
>     foo(aaa());
>
> With inlining, I suspect we can get the compiler to not make any extra copies. It's not that different from NRVO. And as a marvy bonus, no weird semantic problems (as Atila mentioned).

How do you add this overload for the following?


void foo(ref T t) { ... }

void function(ref int) func = &foo;
int aaa();

func(aaa()); // err



March 27, 2018
On Monday, 26 March 2018 at 14:40:03 UTC, Atila Neves wrote:
> C++ T&& (Rvalue reference) -> D T

Not really, in C++ it is an actual reference and you get to choose which function actually does the move. In D it just does the copy when passed to the function. So you can't do this in D.

void bar(T&& t)
{
    // actually move contents of T
}

void foo(T&& t)
{
    bar(std::forward<T>(t)); // Can't do this in D without making another actual copy cause it isn't a reference
}

> If replacing const T& with T chafes, I understand. I used to feel that way too. It's _possible_ that would incur a penalty in copying/moving, but IME the cost is either 0, negligible, or negative (!).
>
> As mentioned above, if calling C++ code there's no choice about using T instead of const T&, so for pragmatic reasons that should be allowed. But only there, because...
>
>> Can you please explain these 'weirdities'?
>> What are said "major unintended consequences"?
>
> Rvalue references. They exist because of being able to bind temporaries to const T& in C++, which means there's no way of knowing if your const T& was originally a temporary or not. To disambiguate C++11 introduced the type system horror that are rvalue references (which also do double-duty in enabling perfect forwarding!).

What's a concrete example that you would be required to know whether a const& is a temporary or not. I don't really see it otherwise. The solution everyone is saying to use as an alternative would be the literal case of how it would be implemented in the language.

> D doesn't have or need rvalue references _because_ of not allowing temporaries to bind to ref const(T). You get move semantics in D without the pain. That's a win in my book.
>
> Atila

I've come across a few pains of such. It make be easier to use but it comes at a performance hit. In part binaries become huge because of how "init" is implemented.

struct StaticArray(T, size_t capacity)
{
    size_t length;
    T[capacity] values;
}

Copying the above structure copies unnecessary data for any move/copy operation. Eg when length = 0, it'll still copy everything. This includes initialization.
March 26, 2018
On 26 March 2018 at 15:48, Walter Bright via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
> On 3/26/2018 12:24 PM, Manu wrote:
>>
>> On 26 March 2018 at 07:40, Atila Neves via Digitalmars-d
>>>
>>> C++ const T& -> D T
>>
>>
>> Yeah, no... T may be big. Copying a large thing sucks. Memory copying
>> is the slowest thing computers can do.
>> As an API author, exactly as in C++, you will make a judgement on a
>> case-by-case basis on this matter. It may be by-value, it may be by
>> const-ref. It depends on a bunch of things, and they are points for
>> consideration by the API author, not the user.
>
>
> Copying does suck, I agree. Consider the following:
>
>     void foo(T t) { foo(t); } <= add this overload
>     void foo(ref T t) { ... }
>     T aaa();
>
>     foo(aaa());
>
> With inlining, I suspect we can get the compiler to not make any extra copies. It's not that different from NRVO. And as a marvy bonus, no weird semantic problems (as Atila mentioned).

It's a terrible experience to add 2^n overloads, just to abuse the
parameter list (as in your example) to create the temporary in an
implicit manner. The compiler can easily create the exact same
temporary without requiring a bunch of overloads to be declared.
What's the advantage of requiring that effort from the user, rather
than just doing it at the call site?
There's side effects to that hack too. Now there are overloads; so
taking function pointers becomes awkward (might be important in some
cases). If the interface has a binary boundary (static lib/dll) then
what does that look like with respect to inlining? Where do the
overloads go if the function is virtual?
I already know the answers to these questions, but the point is,
there's a whole lot more baggage introduced into the scene that just
doesn't need to be there.

So, while I agree that's an existing workaround, it kinda misses the point. This thread isn't about inlining, it's about NOT inlining. You don't use ref args unless you have a reason to, and that reason is likely to have friction with that particular work-around.