"ref const" parameters in functions

Jonathan M Davis jmdavisProg at gmx.com
Sun Apr 1 01:21:50 PDT 2012


On Sunday, April 01, 2012 09:10:58 L-MAN wrote:
> On Saturday, 31 March 2012 at 21:42:05 UTC, Jonathan M Davis
> 
> wrote:
> > On Saturday, March 31, 2012 23:25:51 L-MAN wrote:
> >> Hello everybody!
> >> 
> >> I'm trying to use some function FN like this:
> >> 
> >> struct X
> >> {
> >> 
> >>   protected double _x;  // double type for example
> >>   public @property double X() const { return _x; }
> >>   
> >>   // ctor
> >>   public this(double x) { _x = x; } // double type for example
> >>   
> >>   void FN(ref const(double) in_x) // double type for example
> >>   {
> >>   
> >>     // ... do some operations with _x
> >>   
> >>   }
> >> 
> >> }
> >> 
> >> main(..)
> >> {
> >> 
> >>   ....
> >>   X x = X(20);
> >>   
> >>   x.FN(30); // getting an error
> >> 
> >> }
> >> 
> >>   why x.FN(20) gets me an error?
> >>   the construction "ref const(double)" or "ref
> >> 
> >> immutable(double)"
> >> must be an rvalue by default I think, but in FN parameter the
> >> compiler expects an lvalue...
> >> 
> >>   this strategy is the way to some unnecessary copy operations,
> >> 
> >> when value 20 (or some big struct instead of it) will copy to
> >> the
> >> stack..
> >> 
> >> How can I resolve this problem?
> > 
> > Unlike C++, const ref _must_ be an lvalue just like ref. If you
> > use auto ref
> > instead of const ref, the compiler is supposed to choose
> > between ref and non-
> > ref based on which it thinks would be more efficient, but it
> > currently only
> > works with templated types.
> > 
> > You can duplicate the function and have a version whose
> > parameter is const ref
> > and one which is not, but be careful if you try and make the
> > non-const ref
> > version call the const ref version (to avoid duplicating the
> > function's body)
> > and make sure that you don't get infinite recursion. It usually
> > works, but I've
> > run into cases before where I ended up with infinite recursion,
> > so make sure
> > that you have unit tests which check.
> > 
> > Regardless, if you're dealing with a primitive type like
> > double, don't bother
> > with const ref. It's not going to be more efficient. It's with
> > large structs
> > that you can see a difference.
> > 
> > - Jonathan M Davis
> 
> Thank you for reply, but how can I exclude some copy operations?
> Look at this sample:
> 
> struct ABC // simple class
> {
> 	private double _d=0;
> 	@property
> 	{
> 		double D() const { return _d; }
> 		double D(double val) { return _d=val; }
> 	}
> 
> 	this(double d)
> 	{
> 		_d = d;
> 		writeln(" *** ctor ABC, d = ", _d);
> 	}
> 
> 	ABC opAdd(ABC abc)
> 	{
> 		//ABC temp = ABC(this._d+abc._d);
> 		//return temp;
> 		return ABC(this._d+abc._d);
> 	}
> 
> 	ABC opMul(double d)
> 	{
> 		//ABC temp = ABC(this._d*d);
> 		//return temp;
> 		return ABC(this._d*d);
> 	}
> 
> 	ref ABC opAssign(ABC abc)
> 	{
> 		this._d = abc.D;
> 		return this;
> 	}
> 
> 	~this()
> 	{
> 		writeln(" *** dtor ABC, d = ", _d);
> 	}
> }
> 
> 
> struct F {
> 
> 	private ABC _abc;
> 
> 	@property double D() const { return _abc.D; }
> 
> 	public this(ABC abc)
> 	{
> 		_abc = abc;
> 
> 		//abc.D=90;
> 		//pnt.X = 30;
> 	}
> }
> 
> 
> void main(string[] args)
> {
>    ABC abc1 = ABC(10);
>    ABC abc2 = ABC(20);
>    F f = F(abc1+abc2*20.0);
>    writeln(f.D);
>   .......
> }
> 
> Operation abc1+abc2*20.0 consists of 2 ABC copy:
> 1. Copy result of abc2*20.0 temp value to opAdd function
> 2. Copy result of abc1+abc2*20.0 temp2 value to F() ctor
> two temp variables and two copy operations.
> But if I can use "ref const" rvalue operations 0 copies will be
> need:
>    abc2*20.0 creates temp valiable $$$temp
>    opAdd(ref const ABC abc)  use a ref rvalue to $$$temp, is not a
> copy
>    opAdd return a $$$temp2 that use as ref rvalue for F() ctor
> And try to imagine how many unnecessary copies of ABC will create
> a simple operation like this:
>   ((abc1+abc2*20.0)/0.25) + (abc1/abc2)*(abc2-1)....
>    it killing a time of application, especially if time is
> critical.
> 
> The constructions "auto ref" in return functions values are not
> working well, if I use it in a "auto ref opAdd(ref const ABC)"
> "auto ref opMul(double d)" " F.this(ref const ABC)" to exclude
> some copy operations it gives me a strange result :-((

You misunderstood what I meant by auto ref. I meant that you use auto ref 
instead of const ref. e.g.

void func(auto ref T param) {}

At present, func would have to be templated to use auto ref

void func(T)(auto ref T param) {}

but eventually, auto ref shouldn't need the function to be templated. 
Returning auto ref is completely different. It says that the return type is 
inferred (just like with returning auto by itself) except that it's a 
reference to the type which is inferred. And that requires that the value 
being returned by an lvalue which is _not_ a local variable (because in all 
cases except for auto ref on a parameter, ref must refer to an lvalue, and if 
you return a ref to a local variable, you're going to get undefined behavior, 
because you're returned a reference to a variable which no longer exists). You 
can return ref from a function if you're returning the object's this pointer, 
but that's generally all.

If you're really paranoid about copying, then you're going to need to either 
us  auto ref on your parameters (which currently means that the functions must 
be templated), or you need to use const ref (which then requires that you use 
lvalues with those functions). Something like

 ((abc1+abc2*20.0)/0.25) + (abc1/abc2)*(abc2-1)....

makes _no_ sense if you're trying to avoid copying. By definition, those 
operations must create temporaries, because they're returning new values. 
That's exactly what happens with the built-in types. +, *, /, etc. all create 
temporaries. You need to use +=, *=, /=, etc if you don't want to create 
temporaries. The same would be true in C++.

But honestly, in your example, there is _no_ reason to worry about it. You 
have a struct that holds one double, and that's it. So, it's going to be about 
as efficient as if all of those operations were operating on a double rather 
than a struct. If your struct had ten different variables, then maybe you'd 
need to be worried about copying, but not with just one.

Regardless, if you want to avoid creating temporaries, you can't use operators 
which return new values. You need to use the opOpAssign operators, which means 
that your code is going to look very different, because you won't be able to 
create expressions like the one above.

By using auto ref and/or const ref, you can create functions which don't make 
copies when passed a variable, but as long as you're using functions/operators 
which return new values, you're still going to be creating temporaries.

- Jonathan M Davis


More information about the Digitalmars-d-learn mailing list