digitalmars.D - About ref used for performance reasons with struct
- deadalnix (51/51) Feb 10 2013 Ok, We have 2 usages of ref : when you actually need to modify
- deadalnix (4/55) Feb 10 2013 EDIT: I forgot one condition for the callee, it is disallowed to
- Namespace (5/5) Feb 11 2013 But what if we want to pass a struct with intent as value?
- deadalnix (25/30) Feb 11 2013 That is the whole point, everything appear as if it is passed by
- Namespace (6/36) Feb 11 2013 I see. Nice idea. But what is the criterion for the compiler to
- deadalnix (14/20) Feb 11 2013 A good rule of thumb to know when to pass by ref for perf is :
- Namespace (9/20) Feb 11 2013 I thought of something like this:
- deadalnix (5/8) Feb 11 2013 Compiler now inline even when inline isn't present, and don't
- Namespace (5/9) Feb 11 2013 That was no criticism.
- Steven Schveighoffer (4/7) Feb 11 2013 Array slices are 2*sizeof(size_t). I would expect them to be always
- deadalnix (8/15) Feb 11 2013 First, they alway appears to be copied from the dev perspective.
- Namespace (28/28) Feb 11 2013 How about this approach:
- Paulo Pinto (12/63) Feb 11 2013 How does this work in the context of modular programming?
- deadalnix (3/12) Feb 11 2013 I think it is reasonable to say that this will not work is the
- Jakob Ovrum (6/9) Feb 11 2013 I touched on the idea in a StackOverflow answer a while back:
- Minas Mina (4/55) Feb 11 2013 +1
- kinke (40/40) Feb 11 2013 +1, I've been thinking about similar concepts as well.
- deadalnix (4/10) Feb 11 2013 That is overly restrictive. See sample code given in this thread,
- kinke (9/19) Feb 11 2013 Not sure what you mean by A containing any indirections, but I'd
- kinke (49/78) Feb 12 2013 Thinking about it some more, I'm fairly convinced the proposal in
- Namespace (12/12) Feb 12 2013 Read my approach. I suggest something like A& a. It's short and
- kinke (36/48) Feb 12 2013 ... where A& stands for a reference to an A instance. But in your
- Namespace (11/11) Feb 12 2013 Don't get me wrong, I also hope that this unfortunate and lengthy
- kinke (19/31) Feb 12 2013 I tried my best a while back, but they sadly didn't take part in
- kinke (2/4) Feb 12 2013 I.e., something like this:
- Namespace (3/7) Feb 12 2013 http://forum.dlang.org/thread/nirfuenixutsbgyrcsla@forum.dlang.org
- kinke (29/38) Feb 12 2013 Sorry, please discard these lvalue cases, they aren't safe, i.e.,
- Michel Fortin (18/20) Feb 11 2013 When designing the ABI, you can either create the copy on the caller's
- Namespace (2/7) Feb 11 2013 That's how 'auto ref' currently works. But I do not think that
- deadalnix (4/13) Feb 11 2013 That is blatantly wrong.
- Maxim Fomin (13/29) Feb 11 2013 I expect this to be a nightmare for both users, who would
- deadalnix (11/43) Feb 11 2013 The optimization cannot be used on opaques calls. This include
- John Colvin (2/4) Feb 11 2013 in order to do easier debugging / to avoid implementation bugs
- deadalnix (2/6) Feb 12 2013 This is where the difference between allowing and enforcing lies.
- John Colvin (6/14) Feb 12 2013 Of course, but it's always nice to be able to disable any
- Namespace (5/5) Feb 11 2013 I'm a bit confused now. It's your intention to create (worst
- Era Scarecrow (6/11) Feb 11 2013 For some reason I doubt that's what he means. Seems more often
- Nick Sabalausky (6/21) Feb 11 2013 To be honest, up until recently, I mistakenly thought the compiler DID
- Era Scarecrow (26/37) Feb 11 2013 I'm pretty sure static/fixed arrays are more like structs, and
- Era Scarecrow (29/33) Feb 11 2013 Now had the function been 'void fun(char[] test)', then passing
- jerro (2/8) Feb 11 2013 Maybe you were thinking about named return value optimization
- =?UTF-8?B?QWxpIMOHZWhyZWxp?= (13/20) Feb 11 2013 There is a corresponding optimization for parameters, which can be
- Peter Alexander (18/21) Feb 11 2013 struct Foo { int x, y; }
- deadalnix (5/26) Feb 12 2013 I guess the constraint are too lax. Is it possible to get around
- Martin Nowak (30/75) Feb 12 2013 You can easily create a function that turns values into rvalues if it's
- Martin Nowak (4/6) Feb 12 2013 Actually there is also a nice template based implementation of this in
- Namespace (2/9) Feb 13 2013 But that does not solve the problem. And it's not pleasant to use.
Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side : The caller is free to call the ref version of the function unless (rules evaluate in order) : - The argument is an rvalue (in such case, no postblit is executed as well). - The argument is shared. - The argument's postblit in not pure (weakly). The callee isn't modified for the vanilla function, but must create a local copy of the argument in the following reasons in the ref version : - The argument is binded to a mutable ref.(even as hidden argument as for member method). - The argument is actually modified. - address of anything coming from the struct is taken. The compiler is free to apply such treatment only in a branch of the callee, ie : // Compiler choose to create an alternative ref version for performance. void foo(MyStruct s) { if(condition) { // Operation require a copy to not alter what the caller see. // A s is copied on stack and postblit is called. s.field = 5; } else { // No operation requiring local copy is performed. // No local copy is created. } } The compiler is however disallowed to create multiple copies in the callee. If several branches requires it, then the copy have to be made in a common branch. Note that the compiler don't HAVE to do this, but is allowed to. Modifying the spec in such way allow the compiler to avoid many copies of struct let us get rid of most ref parameters, keeping them for what they really are for.
Feb 10 2013
On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side : The caller is free to call the ref version of the function unless (rules evaluate in order) : - The argument is an rvalue (in such case, no postblit is executed as well). - The argument is shared. - The argument's postblit in not pure (weakly). The callee isn't modified for the vanilla function, but must create a local copy of the argument in the following reasons in the ref version : - The argument is binded to a mutable ref.(even as hidden argument as for member method). - The argument is actually modified. - address of anything coming from the struct is taken. The compiler is free to apply such treatment only in a branch of the callee, ie : // Compiler choose to create an alternative ref version for performance. void foo(MyStruct s) { if(condition) { // Operation require a copy to not alter what the caller see. // A s is copied on stack and postblit is called. s.field = 5; } else { // No operation requiring local copy is performed. // No local copy is created. } } The compiler is however disallowed to create multiple copies in the callee. If several branches requires it, then the copy have to be made in a common branch. Note that the compiler don't HAVE to do this, but is allowed to. Modifying the spec in such way allow the compiler to avoid many copies of struct let us get rid of most ref parameters, keeping them for what they really are for.EDIT: I forgot one condition for the callee, it is disallowed to copy the struct or any part of it that contain reference/pointer is the postblit isn't strongly pure.
Feb 10 2013
But what if we want to pass a struct with intent as value? With this solution, we don't have much control. In general, I like the idea, but we should mark such parameter with '&' or whatever, so at least we can still take some influence.
Feb 11 2013
On Monday, 11 February 2013 at 10:04:55 UTC, Namespace wrote:But what if we want to pass a struct with intent as value? With this solution, we don't have much control. In general, I like the idea, but we should mark such parameter with '&' or whatever, so at least we can still take some influence.That is the whole point, everything appear as if it is passed by value. struct A { uint member; this(this) { // Very long but pure operation. } } void main() { A a; foo(a); assert(a.member = 0); // Pass. } void foo(A a) { // Compiler can choose to pass by ref here. bar(a); } void bar(A a) { // If compiler choose to pass by ref, then it is bar responsibility to create a copy. A smarter move from the compiler is to mark bar as non electable for such optimization. a.member = 5; } In the example above, the compiler is allowed to pass a by ref to foo. As a consequence, the very long operation within the postblit can only be executed once, instead of 2.
Feb 11 2013
On Monday, 11 February 2013 at 10:53:40 UTC, deadalnix wrote:On Monday, 11 February 2013 at 10:04:55 UTC, Namespace wrote:I see. Nice idea. But what is the criterion for the compiler to pass 'a' by ref? I'm still therefor, to mark such paramters with '&' or something but that's maybe my C++ background. I'm curious to see if Walter or Andrei say something to the idea.But what if we want to pass a struct with intent as value? With this solution, we don't have much control. In general, I like the idea, but we should mark such parameter with '&' or whatever, so at least we can still take some influence.That is the whole point, everything appear as if it is passed by value. struct A { uint member; this(this) { // Very long but pure operation. } } void main() { A a; foo(a); assert(a.member = 0); // Pass. } void foo(A a) { // Compiler can choose to pass by ref here. bar(a); } void bar(A a) { // If compiler choose to pass by ref, then it is bar responsibility to create a copy. A smarter move from the compiler is to mark bar as non electable for such optimization. a.member = 5; } In the example above, the compiler is allowed to pass a by ref to foo. As a consequence, the very long operation within the postblit can only be executed once, instead of 2.
Feb 11 2013
On Monday, 11 February 2013 at 12:16:14 UTC, Namespace wrote:I see. Nice idea. But what is the criterion for the compiler to pass 'a' by ref? I'm still therefor, to mark such paramters with '&' or something but that's maybe my C++ background. I'm curious to see if Walter or Andrei say something to the idea.A good rule of thumb to know when to pass by ref for perf is : - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience. - The struct have a postblit. The compiler doing it is superior to the user marking everything : - The user may forget, getting slow down for nothing. - The user can get it wrong the other way around (pass by ref when it isn't appropriate). - This is really hard to get right in generic code. - This is relatively easy to automate. The obvious drawback is possible code bloat, but that isn't that bad (and can be used as heuristic by the compiler to decide if it should pass by ref or not).
Feb 11 2013
A good rule of thumb to know when to pass by ref for perf is : - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience. - The struct have a postblit. The compiler doing it is superior to the user marking everything : - The user may forget, getting slow down for nothing. - The user can get it wrong the other way around (pass by ref when it isn't appropriate). - This is really hard to get right in generic code. - This is relatively easy to automate.I thought of something like this: void foo(A& a) { // the compiler choose if A should pass by ref or by value. } void bar(A a) { // normal behaviour. } That is identical with 'inline' in C++. You _can_ declare a function as inline, but finally the compiler choose if he really inline the function or not.
Feb 11 2013
On Monday, 11 February 2013 at 16:27:29 UTC, Namespace wrote:That is identical with 'inline' in C++. You _can_ declare a function as inline, but finally the compiler choose if he really inline the function or not.Compiler now inline even when inline isn't present, and don't inline when inline is present but this don't make sense. In other terms, inline is the perfect example of why defining stuff like that explicitly make no sense.
Feb 11 2013
Compiler now inline even when inline isn't present, and don't inline when inline is present [...]That's what I said.In other terms, inline is the perfect example of why defining stuff like that explicitly make no sense.That was no criticism. I like to explicitly mark such things, but whatever, I like the idea anyway. I still hope for a response from Walter or Andrei.
Feb 11 2013
On Mon, 11 Feb 2013 10:08:52 -0500, deadalnix <deadalnix gmail.com> wrote:A good rule of thumb to know when to pass by ref for perf is : - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience.Array slices are 2*sizeof(size_t). I would expect them to be always copied and not ref'd. -Steve
Feb 11 2013
On Monday, 11 February 2013 at 16:51:22 UTC, Steven Schveighoffer wrote:On Mon, 11 Feb 2013 10:08:52 -0500, deadalnix <deadalnix gmail.com> wrote:First, they alway appears to be copied from the dev perspective. That why I put bunch of restrictions in the proposal. Second, slice are 2*size_t and are not mixed entities (from CPU perspective, pointer are integers). So I don't have numbers, but I expect slice to be faster when passed by copy than when passed by ref.A good rule of thumb to know when to pass by ref for perf is : - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience.Array slices are 2*sizeof(size_t). I would expect them to be always copied and not ref'd.
Feb 11 2013
On Monday, 11 February 2013 at 16:56:38 UTC, deadalnix wrote:On Monday, 11 February 2013 at 16:51:22 UTC, Steven Schveighoffer wrote:The idea of compiler choosing the optimal between: 'T t' and 'ref const(T) t' has been brought up a few times. Here is one attempt at getting numbers to see where a cutoff might be. http://forum.dlang.org/thread/opufykfxwkkjchqcwgrg forum.dlang.org Based on this, and to avoid the boilerplate of read accessors, I use the following heuristic. If others have more friendly ways I'd be interested. Thanks Dan /** Discriminates a pass type by its size */ template PrefersPassByRef(T) { static if(isAssociativeArray!T || isDynamicArray!T) { enum PrefersPassByRef = false; } else static if(T.sizeof > 16 || hasAliasing!T) { enum PrefersPassByRef = true; } else { enum PrefersPassByRef = false; } } /** Discriminates a pass type by its size */ template PreferredPassType(T) { static if(PrefersPassByRef!T) { enum PreferredPassType = `const ref `~T.stringof; } else { enum PreferredPassType = T.stringof; } } /** Provides mixin for making a field read only. * For example mixin(ReadOnly!_fieldName) provides a getter named fieldName. */ template ReadOnly(alias name) { enum v = name.stringof; enum p = name.stringof[1..$]; enum prefersReference = PrefersPassByRef!(typeof(name)); static if(prefersReference) { mixin(` public property auto ref `~p~`() const { return `~v~`; } `); } else { mixin(` public property auto `~p~`() const { return `~v~`; } `); } }On Mon, 11 Feb 2013 10:08:52 -0500, deadalnix <deadalnix gmail.com> wrote:First, they alway appears to be copied from the dev perspective. That why I put bunch of restrictions in the proposal. Second, slice are 2*size_t and are not mixed entities (from CPU perspective, pointer are integers). So I don't have numbers, but I expect slice to be faster when passed by copy than when passed by ref.A good rule of thumb to know when to pass by ref for perf is : - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience.Array slices are 2*sizeof(size_t). I would expect them to be always copied and not ref'd.
Feb 11 2013
On Monday, 11 February 2013 at 22:40:44 UTC, Dan wrote:On Monday, 11 February 2013 at 16:56:38 UTC, deadalnix wrote:This is a lot o code and don't handle some cases. For instance struct A { long a; double b; } is generally better passed by ref, where struct A { long a; long b; } is better passed by value.On Monday, 11 February 2013 at 16:51:22 UTC, Steven Schveighoffer wrote:The idea of compiler choosing the optimal between: 'T t' and 'ref const(T) t' has been brought up a few times. Here is one attempt at getting numbers to see where a cutoff might be. http://forum.dlang.org/thread/opufykfxwkkjchqcwgrg forum.dlang.org Based on this, and to avoid the boilerplate of read accessors, I use the following heuristic. If others have more friendly ways I'd be interested. Thanks Dan /** Discriminates a pass type by its size */ template PrefersPassByRef(T) { static if(isAssociativeArray!T || isDynamicArray!T) { enum PrefersPassByRef = false; } else static if(T.sizeof > 16 || hasAliasing!T) { enum PrefersPassByRef = true; } else { enum PrefersPassByRef = false; } } /** Discriminates a pass type by its size */ template PreferredPassType(T) { static if(PrefersPassByRef!T) { enum PreferredPassType = `const ref `~T.stringof; } else { enum PreferredPassType = T.stringof; } } /** Provides mixin for making a field read only. * For example mixin(ReadOnly!_fieldName) provides a getter named fieldName. */ template ReadOnly(alias name) { enum v = name.stringof; enum p = name.stringof[1..$]; enum prefersReference = PrefersPassByRef!(typeof(name)); static if(prefersReference) { mixin(` public property auto ref `~p~`() const { return `~v~`; } `); } else { mixin(` public property auto `~p~`() const { return `~v~`; } `); } }On Mon, 11 Feb 2013 10:08:52 -0500, deadalnix <deadalnix gmail.com> wrote:First, they alway appears to be copied from the dev perspective. That why I put bunch of restrictions in the proposal. Second, slice are 2*size_t and are not mixed entities (from CPU perspective, pointer are integers). So I don't have numbers, but I expect slice to be faster when passed by copy than when passed by ref.A good rule of thumb to know when to pass by ref for perf is : - The struct is big, or contains mixed entities (floats and ints). 2*size_t seems like a good heuristic from my experience.Array slices are 2*sizeof(size_t). I would expect them to be always copied and not ref'd.
Feb 12 2013
On Tuesday, 12 February 2013 at 17:33:08 UTC, deadalnix wrote:This is a lot o codeFortunately, like a lot of code you don't need to look at it to use it. That is the purpose of it. struct ReductionMap(K, V, alias reduction_op = "+", alias init = 0) { mixin ReadOnly!_dataMap; mixin ReadOnly!_reduction; alias V[K] DataMap; ... private { DataMap _dataMap; V _reduction = init; } }and don't handle some cases. For instanceI did not say it handled everything and if you have strategies that will allow me to simply, declaratively get a better selection between the two pass types I'd incorporate it. If and until the language changes, what other choices are there? I just used the numbers to come up with a heuristic, which I imagine, given the state of things is how any one makes the choice. The only difference is the choice is somewhat formalized.struct A { long a; double b; } is generally better passed by ref, where struct A { long a; long b; } is better passed by value.Any performance numbers? Even if that were true, is it realistic to expect a compiler to get it right each time? Thanks Dan
Feb 12 2013
How about this approach: You almost need functions, which takes structs as an rvalue, for things like: ---- struct A { } void foo(A a) { } foo(A()); ---- So what's about something like this (Yes I mark it with '&'): ---- struct A { } void foo(A& a) { } ---- The compiler decides at compile time whether A should be taken by ref or by value. So for example, if A.sizeof > 16, it changes to: ---- void foo(ref A a) { ---- otherwise: ---- void foo(A a) { ---- In the latter case nothing happens for calls like: foo(A()); But in the former case the call: foo(A()); is changed to: auto __temp = A(); foo(__temp);
Feb 11 2013
On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side : The caller is free to call the ref version of the function unless (rules evaluate in order) : - The argument is an rvalue (in such case, no postblit is executed as well). - The argument is shared. - The argument's postblit in not pure (weakly). The callee isn't modified for the vanilla function, but must create a local copy of the argument in the following reasons in the ref version : - The argument is binded to a mutable ref.(even as hidden argument as for member method). - The argument is actually modified. - address of anything coming from the struct is taken. The compiler is free to apply such treatment only in a branch of the callee, ie : // Compiler choose to create an alternative ref version for performance. void foo(MyStruct s) { if(condition) { // Operation require a copy to not alter what the caller see. // A s is copied on stack and postblit is called. s.field = 5; } else { // No operation requiring local copy is performed. // No local copy is created. } } The compiler is however disallowed to create multiple copies in the callee. If several branches requires it, then the copy have to be made in a common branch. Note that the compiler don't HAVE to do this, but is allowed to. Modifying the spec in such way allow the compiler to avoid many copies of struct let us get rid of most ref parameters, keeping them for what they really are for.How does this work in the context of modular programming? Does the compiler generate multiple code for each case, to make the code work regardless of what is decided at the call site? Or is the code written in a canonical form inside the module, which gets to be rewritten when all modules are linked into the final binary? By modules I mean the case where you only have a .di file + binary code for the module. For me the right form to distribute libraries. -- Paulo
Feb 11 2013
On Monday, 11 February 2013 at 10:21:30 UTC, Paulo Pinto wrote:How does this work in the context of modular programming? Does the compiler generate multiple code for each case, to make the code work regardless of what is decided at the call site? Or is the code written in a canonical form inside the module, which gets to be rewritten when all modules are linked into the final binary? By modules I mean the case where you only have a .di file + binary code for the module. For me the right form to distribute libraries.I think it is reasonable to say that this will not work is the compiler don't have callee's implementation.
Feb 11 2013
On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one.I touched on the idea in a StackOverflow answer a while back: http://stackoverflow.com/a/8515844 The meaning of 'in' means such parameters can in theory be optimized if the compiler has the proper sources, so it would seem right to leverage it.
Feb 11 2013
On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side : The caller is free to call the ref version of the function unless (rules evaluate in order) : - The argument is an rvalue (in such case, no postblit is executed as well). - The argument is shared. - The argument's postblit in not pure (weakly). The callee isn't modified for the vanilla function, but must create a local copy of the argument in the following reasons in the ref version : - The argument is binded to a mutable ref.(even as hidden argument as for member method). - The argument is actually modified. - address of anything coming from the struct is taken. The compiler is free to apply such treatment only in a branch of the callee, ie : // Compiler choose to create an alternative ref version for performance. void foo(MyStruct s) { if(condition) { // Operation require a copy to not alter what the caller see. // A s is copied on stack and postblit is called. s.field = 5; } else { // No operation requiring local copy is performed. // No local copy is created. } } The compiler is however disallowed to create multiple copies in the callee. If several branches requires it, then the copy have to be made in a common branch. Note that the compiler don't HAVE to do this, but is allowed to. Modifying the spec in such way allow the compiler to avoid many copies of struct let us get rid of most ref parameters, keeping them for what they really are for.+1 It's really ugly to write "const ref Blah blah" all the time when the compiler could it.
Feb 11 2013
+1, I've been thinking about similar concepts as well. This would be particularly useful for Win64. The Win64 ABI requires all structs > 64 bits (or of a size which is not a power of 2) to be passed by reference. So if a function foo(BigStruct s) is invoked, the caller first allocates a copy of s for the callee on its own stack (16-bytes aligned) and then passes the callee a pointer to the copy (in a register or on the parameters stack), instead of passing a copy of s directly on the parameters stack. It seems to me that the whole idea of this is to encourage byref passing and copying arguments only if required, otherwise it doesn't make much sense performance-wise (higher stack memory usage due to the additional pointer and need for dereferencing the pointer parameter). I'd propose a small change so that suited structs are passed transparently byref only if the parameter is not mutable, e.g., for a function foo(const BigStruct s). The compiler would therefore not need to analyze the code flow in the callee to determine if the parameter is modified and hence a copy is needed. The compiler would nevertheless need to be quite smart though: --- struct MyBigStruct { double a, b, c; } double foo(const MyBigStruct s, double* x) { *x = s.a; return s.b; } // naive optimization by byref passing double foo_ref(const ref MyBigStruct s, double* x) { *x = s.a; return s.b; } MyBigStruct s = { 1, 2, 3 }; double* x = &s.b; auto bla = foo(s, x); // returns 2; now s.b = 1 s.b = 2; // reset s.b to 2 bla = foo_ref(s, x); // returns 1! (the new s.b = 1) --- So the compiler would need to prove there is no way the argument can be modified and handle tricky aliasing issues accordingly.
Feb 11 2013
On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:I'd propose a small change so that suited structs are passed transparently byref only if the parameter is not mutable, e.g., for a function foo(const BigStruct s). The compiler would therefore not need to analyze the code flow in the callee to determine if the parameter is modified and hence a copy is needed.That is overly restrictive. See sample code given in this thread, foo couldn't get the struct by ref in such a case if A contains any indirection.
Feb 11 2013
On Monday, 11 February 2013 at 15:12:07 UTC, deadalnix wrote:On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:Not sure what you mean by A containing any indirections, but I'd simply rewrite your example as follows: void foo(const A a) { // byref passing desirable bar(a); // bar's parameter is mutable => pass a copy (byval) } void bar(A a) { // byval passing a.member = 5; }I'd propose a small change so that suited structs are passed transparently byref only if the parameter is not mutable, e.g., for a function foo(const BigStruct s). The compiler would therefore not need to analyze the code flow in the callee to determine if the parameter is modified and hence a copy is needed.That is overly restrictive. See sample code given in this thread, foo couldn't get the struct by ref in such a case if A contains any indirection.
Feb 11 2013
On Monday, 11 February 2013 at 14:54:48 UTC, kinke wrote:I'd propose a small change so that suited structs are passed transparently byref only if the parameter is not mutable, e.g., for a function foo(const BigStruct s). The compiler would therefore not need to analyze the code flow in the callee to determine if the parameter is modified and hence a copy is needed. The compiler would nevertheless need to be quite smart though: --- struct MyBigStruct { double a, b, c; } double foo(const MyBigStruct s, double* x) { *x = s.a; return s.b; } // naive optimization by byref passing double foo_ref(const ref MyBigStruct s, double* x) { *x = s.a; return s.b; } MyBigStruct s = { 1, 2, 3 }; double* x = &s.b; auto bla = foo(s, x); // returns 2; now s.b = 1 s.b = 2; // reset s.b to 2 bla = foo_ref(s, x); // returns 1! (the new s.b = 1) --- So the compiler would need to prove there is no way the argument can be modified and handle tricky aliasing issues accordingly.Thinking about it some more, I'm fairly convinced the proposal in this thread is just too dangerous. Proving the argument passed transparently byref cannot be modified is just too complex imo; e.g., it could be modified by the callee via aliasing issues shown in the example above, or it could be modified by another thread while the callee is running, hence modifying the callee's parameter at the same time! I think the intended move semantics (byval passing for small structs w/o postblit constructor/destructor, otherwise byref) are only really safe if the argument is an rvalue - the rvalue is guaranteed not to be used after the call, so potential modifications are not visible for the caller, and the rvalue is guaranteed not to be used simultaneously in another thread. Certain lvalue cases could be optimized as well, e.g., if the lvalue is a private variable of the caller (local variable or parameter) AND is not used after the call. --- struct MyBigStruct { double a, b, c; } double bar(MyBigStruct s) { return s.a; } double foo(MyBigStruct s) // s is an lvalue (parameter) { return bar(s); // s NOT used afterwards => byref passing } void main() { MyBigStruct s; // s is an lvalue (local variable) invoke foo(s) in another thread; // s used afterwards => byval if (...) { foo(s); // s used afterwards (next line) => byval s.a += 1; } else foo(s); // s NOT used afterwards => byref } --- So imo parameters need to be denoted by something special like 'auto ref' if a copy is to be elided for performance reasons - not in its current form though (only for templates and leading to code-bloating); instead, rvalues should simply be transformed to lvalues before the call and then passed byref just like ordinary lvalues. And as I've already pointed out a few times in recent discussions, I'm not a fan of 'const auto ref' for not-mutable parameters, so I'd very much like to see 'const ref' for these (allowing rvalues too, just like C++). The function signature therefore clearly indicates that these params are references to the caller's arguments, with all potentially dangerous implications.
Feb 12 2013
Read my approach. I suggest something like A& a. It's short and known from C++. Only in my approach, I suggest a link between the proposal of deadalnix (compiler optimizations) and generally rvalue references. 'const ref' will never work the way as we know it from C++. Walter and Andrei and many others are totally against it. However, 'auto ref' is also not a real solution, because 'auto ref' generates 2^(n - 1) permuationen of the same function - code bloat. 'auto ref' accept of course lvalues and rvalues but you gain no performance.
Feb 12 2013
On Tuesday, 12 February 2013 at 13:59:45 UTC, Namespace wrote:Read my approach. I suggest something like A& a. It's short and known from C++.... where A& stands for a reference to an A instance. But in your approach it denotes either a plain A instance OR a reference to it, so I don't like your suggestion at all, I'm afraid. Additionally, afaik D/DMD (?) already implements move semantics for rvalues, so 1) the goal of your approach is already implemented and 2) the existing optimization doesn't require any special parameter denotation (plain A is fine).Only in my approach, I suggest a link between the proposal of deadalnix (compiler optimizations) and generally rvalue references.Yes, and I'd extend it for the illustrated lvalue cases as well.'const ref' will never work the way as we know it from C++. Walter and Andrei and many others are totally against it.And there are many others totally in favour of it. ;) I'm still waiting for a plausible argument as to why the callee needs to know whether a passed const reference actually references an lvalue or an rvalue. I'm tired of this const ref discussion, and probably many others are too.However, 'auto ref' is also not a real solution, because 'auto ref' generates 2^(n - 1) permuationen of the same function - code bloat.That's what I said basically regarding the current 'auto ref' implementation.'auto ref' accept of course lvalues and rvalues but you gain no performance.Of course you'd gain performance because lvalues would not be copied (just like 'ref' only). But in contrast to 'ref', you'd also be able to pass rvalues directly (byref), without having to overload the function or turn the rvalue manually to an lvalue right before the call. The thing is that with 'auto ref', you have to determine if the struct is suited for copy-elision and then decorate the params manually with 'auto ref'/'const auto ref'/'const ref' ;). deadalnix listed some good reasons why the compiler should do that for us. But I think I also mentioned good reasons as to why the compiler most likely can't due to the huge complexity involved to make sure code doesn't break. Although it is unlikely to break in most cases, an optimization feature such as this needs to work for ALL cases. This is why I suggested applying deadalnix' intended implicit move semantics only for rvalues and the mentioned, safe lvalue cases. Afaik D already implements move semantics for rvalues, but I guess it doesn't optimize the lvalue cases. For all other cases, I think we sadly need to resort to a revisited 'auto ref' approach.
Feb 12 2013
Don't get me wrong, I also hope that this unfortunate and lengthy discussion ends. And I also like the idea that const ref works as in C++. But I'm sure you can convince neither Walter nor Andrei still the core developer team. And I don't quite understand what speaks against my suggestion. If the compiler decides that by value is a better solution, then structs are received by value, otherwise by ref. But the user don't need to worry about it, because, whatever the compiler may decide to do, he adapt calls to these functions automatically. It is in principle nothing more than what you want.
Feb 12 2013
On Tuesday, 12 February 2013 at 15:53:57 UTC, Namespace wrote:Don't get me wrong, I also hope that this unfortunate and lengthy discussion ends. And I also like the idea that const ref works as in C++. But I'm sure you can convince neither Walter nor Andrei still the core developer team.I tried my best a while back, but they sadly didn't take part in the discussion. Just in case, it's summarized in http://forum.dlang.org/thread/zteryxwxyngvyqvukqkm forum.dlang.org. I could live with 'const auto ref' too, but I'd hate having to type these 5 additional characters every time. ;)And I don't quite understand what speaks against my suggestion. If the compiler decides that by value is a better solution, then structs are received by value, otherwise by ref. But the user don't need to worry about it, because, whatever the compiler may decide to do, he adapt calls to these functions automatically.Your proposal, if I understood correctly, restricts deadalnix' approach to rvalues only and additionally requires changing the param from 'A' to 'A&'. But iirc rvalues are already passed directly (moved) in D, i.e., they are not copied - so your approach is 1) simply not needed and 2) would require the A -> 'A&' transform (similar to A -> 'auto ref A').It is in principle nothing more than what you want.No, nothing more, but it's already covered by D, without special syntax. I'd like to see 1) the move optimization applied to safe lvalue cases as well, also without requiring syntactic changes; 2) Kenji's 'auto ref' proposal getting pulled in the near future so that we can manually prevent argument copying for the remaining unsafe lvalue cases.
Feb 12 2013
2) Kenji's 'auto ref' proposal getting pulled in the near future so that we can manually prevent argument copying for the remaining unsafe lvalue cases.Maybe in 2065 - if we're lucky. ;) Read also in my thread that I had posted earlier: auto ref - again. There are also one of the rare responses of Andrei to this topic. This problem isn't solved any time soon.
Feb 12 2013
Maybe in 2065 - if we're lucky. ;)I meant of course dmd version 2.065, not the year 2065. ;)
Feb 12 2013
For all other cases, I think we sadly need to resort to a revisited 'auto ref' approach.I.e., something like this: https://github.com/D-Programming-Language/dmd/pull/1019
Feb 12 2013
On Tuesday, 12 February 2013 at 15:57:42 UTC, kinke wrote:http://forum.dlang.org/thread/nirfuenixutsbgyrcsla forum.dlang.org ;)For all other cases, I think we sadly need to resort to a revisited 'auto ref' approach.I.e., something like this: https://github.com/D-Programming-Language/dmd/pull/1019
Feb 12 2013
On Tuesday, 12 February 2013 at 13:34:10 UTC, kinke wrote:I think the intended move semantics (byval passing for small structs w/o postblit constructor/destructor, otherwise byref) are only really safe if the argument is an rvalue - the rvalue is guaranteed not to be used after the call, so potential modifications are not visible for the caller, and the rvalue is guaranteed not to be used simultaneously in another thread. Certain lvalue cases could be optimized as well, e.g., if the lvalue is a private variable of the caller (local variable or parameter) AND is not used after the call.Sorry, please discard these lvalue cases, they aren't safe, i.e., analog to my earlier example: --- struct BigStruct { long a, b, c; } long foo(const BigStruct s, ref long c) { // s is the callee's copy of the argument c = s.a + s.b + s.c; return s.c; } long foo_ref(const ref BigStruct s, ref long c) { // s is a reference to the caller's argument c = s.a + s.b + s.c; return s.c; } BigStruct s = { 1, 2, 3 }; long r1 = foo(s, s.c); assert(s.c == 6 && r1 == 3); // returns callee's s.c s.c = 3; // reset long r2 == foo_ref(s, s.c); // returns caller's s.c assert(s.c == 6 && r2 == 6); --- So aliasing issues (the caller's argument is modified indirectly via another parameter/global variable) are able to break code when passing _any_ lvalue transparently byref, even if it's not used afterwards. So only rvalues are really safe for move semantics, i.e., they can't be modified by another thread or indirectly by the callee via aliasing AND modifications are not visible to the caller after the call.
Feb 12 2013
On 2013-02-11 06:52:28 +0000, "deadalnix" <deadalnix gmail.com> said:When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter.When designing the ABI, you can either create the copy on the caller's side, or the callee's side. If I'm understanding well, you want to have two versions of each function, one copying on the caller's side, one copying on the callee's side, and the caller's codegen would choose which one to use. Copying on the caller's side is better if you're passing rvalues (because the caller can just always skip the copying), while copying on the callee's side is better when you're passing lvalues, because the caller may decide to skip copying if it doesn't need a copy. I'm not sure creating two versions of each function will fly well with Walter. Actually, you say *two* are needed, but that's for a function with one struct parameter; it's more 2^(number of structs parameters) versions you'd need in the general case. -- Michel Fortin michel.fortin michelf.ca http://michelf.ca/
Feb 11 2013
I'm not sure creating two versions of each function will fly well with Walter. Actually, you say *two* are needed, but that's for a function with one struct parameter; it's more 2^(number of structs parameters) versions you'd need in the general case.That's how 'auto ref' currently works. But I do not think that code bloat was the intention of deadalnix. Quite the contrary.
Feb 11 2013
On Monday, 11 February 2013 at 15:48:32 UTC, Michel Fortin wrote:On 2013-02-11 06:52:28 +0000, "deadalnix" <deadalnix gmail.com> said:That is blatantly wrong. You don't have, the compiler is allowed to, and the copy on callee side isn't mandatory.When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter.When designing the ABI, you can either create the copy on the caller's side, or the callee's side. If I'm understanding well, you want to have two versions of each function, one copying on the caller's side, one copying on the callee's side,
Feb 11 2013
On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side :I expect this to be a nightmare for both users, who would experience new pile of struct bugs and for developers. How much code would this proposal break? What do you mean by 'altered'? Are you taking about two function versions or a single one? If first, how does it work with linking, and if second, how does it work for different situations? What happens when you use function pointer or a delegate? How __traits works with respect to function type in case of such optimization? What happens if compiler cannot deduce from context information required to make such decision? How does it play with variardic functions? And how such optimization can be disabled?
Feb 11 2013
On Monday, 11 February 2013 at 17:15:40 UTC, Maxim Fomin wrote:On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:None.Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side :I expect this to be a nightmare for both users, who would experience new pile of struct bugs and for developers. How much code would this proposal break?What do you mean by 'altered'? Are you taking about two function versions or a single one? If first, how does it work with linking, and if second, how does it work for different situations? What happens when you use function pointer or a delegate? How __traits works with respect to function type in case of such optimization? What happens if compiler cannot deduce from context information required to make such decision? How does it play with variardic functions? And how such optimization can be disabled?The optimization cannot be used on opaques calls. This include function pointer and alike. The only function that is guaranteed to exist is the one that take the struct by value. The other one is an option that the compiler is allowed to choose to make things faster. It is applicable for variadic function for non variadic argument, not for variadic arguments. Finally, why would you disable something that make your code faster ?
Feb 11 2013
On Monday, 11 February 2013 at 17:51:30 UTC, deadalnix wrote:Finally, why would you disable something that make your code faster ?in order to do easier debugging / to avoid implementation bugs
Feb 11 2013
On Monday, 11 February 2013 at 18:38:28 UTC, John Colvin wrote:On Monday, 11 February 2013 at 17:51:30 UTC, deadalnix wrote:This is where the difference between allowing and enforcing lies.Finally, why would you disable something that make your code faster ?in order to do easier debugging / to avoid implementation bugs
Feb 12 2013
On Tuesday, 12 February 2013 at 17:29:07 UTC, deadalnix wrote:On Monday, 11 February 2013 at 18:38:28 UTC, John Colvin wrote:Of course, but it's always nice to be able to disable any optimization globally for any given compilation. I personally like gcc's approach where you have very fine grained control over all optimisations. Something like this should have a compiler switch to enable/disable.On Monday, 11 February 2013 at 17:51:30 UTC, deadalnix wrote:This is where the difference between allowing and enforcing lies.Finally, why would you disable something that make your code faster ?in order to do easier debugging / to avoid implementation bugs
Feb 12 2013
I'm a bit confused now. It's your intention to create (worst case) 2^n permutations of the same function for each potential ref parameter (which would be the same behaviour as 'auto ref') or that the compiler could pass big structs by ref without code bloat?
Feb 11 2013
On Monday, 11 February 2013 at 19:14:15 UTC, Namespace wrote:I'm a bit confused now. It's your intention to create (worst case) 2^n permutations of the same function for each potential ref parameter (which would be the same behavior as 'auto ref') or that the compiler could pass big structs by ref without code bloat?For some reason I doubt that's what he means. Seems more often than not only one or two parameter(s) need to be ref-able, so in most cases with 1 variable two (and only two) functions be made and it choose between the two based on the result. Larger than that and another method would have to be selected.
Feb 11 2013
On Mon, 11 Feb 2013 07:52:28 +0100 "deadalnix" <deadalnix gmail.com> wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain.To be honest, up until recently, I mistakenly thought the compiler DID do this. (Yea, my mistake.) Is it possible I had confused structs with static arrays? Do static arrays do that?
Feb 11 2013
On Monday, 11 February 2013 at 21:45:11 UTC, Nick Sabalausky wrote:On Mon, 11 Feb 2013 07:52:28 +0100 "deadalnix" <deadalnix gmail.com> wrote:I'm pretty sure static/fixed arrays are more like structs, and thereby whole copies are made rather than sending a slice. Let's find out. void main() { char[4] test = "test"; writeln(&test, "- main"); fun(test); if (test == "test") writeln("is passed by value/copy"); else { assert(test == "best", "Something's wrong..."); writeln("is passed by slice/ref"); } } void fun(char[4] test){ assert(test == "test"); writeln(&test, "- fun"); test[0] = 'b'; assert(test == "best"); } Output: 18FDD0- main 18FDAC- fun is passed by value/copyI'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain.To be honest, up until recently, I mistakenly thought the compiler DID do this. (Yea, my mistake.) Is it possible I had confused structs with static arrays? Do static arrays do that?
Feb 11 2013
On Monday, 11 February 2013 at 22:03:52 UTC, Era Scarecrow wrote:Output: 18FDD0- main 18FDAC- fun is passed by value/copyNow had the function been 'void fun(char[] test)', then passing a static/fixed array would have errored and you'd be required to send a slice of it's contents, which then is by ref. Seems using the address doesn't seem to be very helpful, so for slices you'd refer to the ptr of the array. I'm sure there's other tests that can be done... to help with the expected behavior //appended to main fun2(test); if (test == "test") writeln("is passed by value/copy"); else { assert(test == "best", "Something's wrong..."); writeln("is passed by slice/ref"); } } void fun2(char[] test){ assert(test == "test"); //&test points to local test not it's contents writeln(test.ptr, "- fun2"); test[0] = 'b'; assert(test == "best"); } new Output: 18FDD0- main 18FDA4- fun is passed by value/copy 18FDD0- fun2 is passed by slice/ref
Feb 11 2013
To be honest, up until recently, I mistakenly thought the compiler DID do this. (Yea, my mistake.) Is it possible I had confused structs with static arrays? Do static arrays do that?Maybe you were thinking about named return value optimization (which applies to return values, not parameters)?
Feb 11 2013
On 02/11/2013 04:31 PM, jerro wrote:There is a corresponding optimization for parameters, which can be applied only in some cases. The related guideline in C++ is this: If you are going to make a copy of the parameter inside the function anyway, take the parameter by-value. Here is a related article: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/ Especially this part: "Also, although the compiler is normally required to make a copy when a function parameter is passed by value (so modifications to the parameter inside the function can’t affect the caller), it is allowed to elide the copy, and simply use the source object itself, when the source is an rvalue." AliTo be honest, up until recently, I mistakenly thought the compiler DID do this. (Yea, my mistake.) Is it possible I had confused structs with static arrays? Do static arrays do that?Maybe you were thinking about named return value optimization (which applies to return values, not parameters)?
Feb 11 2013
On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain.struct Foo { int x, y; } class Bar { Foo[] foos = [Foo(1, 2)]; void swap(const(Foo) f) { foos[0].x = f.y; foos[0].y = f.x; } } void quux(Bar bar, const(Foo) foo) { bar.swap(foo); } Are the foo parameters allowed to be passed by ref in both these functions? I see no reason why not from your criteria, but doing so changes the program.
Feb 11 2013
On Monday, 11 February 2013 at 23:16:55 UTC, Peter Alexander wrote:On Monday, 11 February 2013 at 06:52:33 UTC, deadalnix wrote:I guess the constraint are too lax. Is it possible to get around that without creating a cascade of special cases ? I frankly don't know, I have to dig more into this.I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain.struct Foo { int x, y; } class Bar { Foo[] foos = [Foo(1, 2)]; void swap(const(Foo) f) { foos[0].x = f.y; foos[0].y = f.x; } } void quux(Bar bar, const(Foo) foo) { bar.swap(foo); } Are the foo parameters allowed to be passed by ref in both these functions? I see no reason why not from your criteria, but doing so changes the program.
Feb 12 2013
On 02/11/2013 07:52 AM, deadalnix wrote:Ok, We have 2 usages of ref : when you actually need to modify informations, and for performance reasons. Let's talk about the second one. Passing by ref to improve performance is not ideal. First this is quite hard to know when it is actually faster to pass by ref and to pass by value, especially in generic code. Secondly it is easy to forget to use ref at some location, and a lot of small performance improvement are lost in the process. Finally, this may be error prone. I'm thinking about it for a while now and I'm now convinced that we should allow the compiler to do that job for us. Let me explain. When a function accept a struct, the compiler is free to use that function, or an altered one taking a reference as parameter. Here are some rules the compiler can use to know which one to call from callee side : The caller is free to call the ref version of the function unless (rules evaluate in order) : - The argument is an rvalue (in such case, no postblit is executed as well). - The argument is shared. - The argument's postblit in not pure (weakly). The callee isn't modified for the vanilla function, but must create a local copy of the argument in the following reasons in the ref version : - The argument is binded to a mutable ref.(even as hidden argument as for member method). - The argument is actually modified. - address of anything coming from the struct is taken. The compiler is free to apply such treatment only in a branch of the callee, ie : // Compiler choose to create an alternative ref version for performance. void foo(MyStruct s) { if(condition) { // Operation require a copy to not alter what the caller see. // A s is copied on stack and postblit is called. s.field = 5; } else { // No operation requiring local copy is performed. // No local copy is created. } } The compiler is however disallowed to create multiple copies in the callee. If several branches requires it, then the copy have to be made in a common branch. Note that the compiler don't HAVE to do this, but is allowed to. Modifying the spec in such way allow the compiler to avoid many copies of struct let us get rid of most ref parameters, keeping them for what they really are for.You can easily create a function that turns values into rvalues if it's opportune. template isBetterToPassAsValue(T) { enum isBetterToPassAsValue = ...; } T cheapPass(T)(ref T t) if (isBetterToPassAsValue!T) { return t; } ref T cheapPass(T)(ref T t) if (!isBetterToPassAsValue!T) { return t; } T cheapPass(T)(T t) if (isBetterToPassAsValue!T) { return move(t); } void foo(T)(auto ref T t) { // ... } void bar() { S1 s1; foo(cheapPass(s1)); // passed by ref S2 s2; foo(cheapPass(s2)); // passed by value }
Feb 12 2013
On 02/13/2013 12:34 AM, Martin Nowak wrote:You can easily create a function that turns values into rvalues if it's opportune.Actually there is also a nice template based implementation of this in phobos (std.algorithm.forward). https://github.com/D-Programming-Language/phobos/blob/8bf6997acfdbe81442e4c3e4c3500b9197ffaaca/std/algorithm.d#L2002
Feb 12 2013
On Tuesday, 12 February 2013 at 23:49:04 UTC, Martin Nowak wrote:On 02/13/2013 12:34 AM, Martin Nowak wrote:But that does not solve the problem. And it's not pleasant to use.You can easily create a function that turns values into rvalues if it's opportune.Actually there is also a nice template based implementation of this in phobos (std.algorithm.forward). https://github.com/D-Programming-Language/phobos/blob/8bf6997acfdbe81442e4c3e4c3500b9197ffaaca/std/algorithm.d#L2002
Feb 13 2013