Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

const ref and rvalue in D

Tags:

ref

d

rvalue

Code

struct CustomReal
{
   private real value;

   this(real value)
   {
      this.value = value;
   }

   CustomReal opBinary(string op)(CustomReal rhs) if (op == "+")
   {
      return CustomReal(value + rhs.value);
   }

   bool opEquals(ref const CustomReal x) const
   {
      return value == x.value; // just for fun
   }
}

// Returns rvalue 
CustomReal Create()
{
   return CustomReal(123.123456);
}

void main()
{
   CustomReal a = Create();
   assert(a == CustomReal(123.123456)); // OK. CustomReal is temporary but lvalue
   assert(a == Create());               // Compilation error (can't bind to rvalue)
   assert(a != a + a);                  // Compilation error (can't bind to rvalue)
}

Compilation error

prog.d(31): Error: function prog.CustomReal.opEquals (ref const const(CustomReal) x) const is not callable using argument types (CustomReal)
prog.d(31): Error: Create() is not an lvalue

http://ideone.com/O8wFc

Questions:

  1. Why const ref can't bind to rvalue? Is it ok?
  2. Do I need to return ref CustomReal or const ref CustomReal from opBinary() to fix this problem? Is it ok?
  3. Is it correct to return a reference to local object created on stack? ref CustomReal Create() { return CustomReal(0.0); }
like image 200
Stas Avatar asked Aug 08 '11 17:08

Stas


2 Answers

The only difference between ref and const ref is that const ref is const and ref is not. Both must take a variable. Neither can take a temporary. This is different from C++ where const T& will take any value of type T - including temporaries.

opBinary cannot return either ref or const ref, because there's no variable to return. It's creating a temporary. The same goes for Create. And creating a local variable with the value that you want to return doesn't help either, because you can't return a reference to a local variable. It would end up referring to a variable which didn't exist anymore.

What you need to do here is add another overload of opEquals:

bool opEquals(CustomReal x) const
{
    return value == x.value; // just for fun
}

With that, your code will compile.

I would point out though that the current situation with opEquals does need to be ironed out a bit. You'll notice that if you only have the overload for opEquals that I gave you and not the one that you currently have, the code fails to compile, and you get an error similar to this:

prog.d(15): Error: function prog.CustomReal.opEquals type signature should be const bool(ref const(CustomReal)) not const bool(CustomReal x)

The compiler is currently overly picky about the exact signature of opEquals for structs (a few other functions - such as toString - have similar issues). It's a known issue and will probably be resolved in the near future. However, for now, just declare both overloads of opEquals. If you compare a CustomReal with a variable, then the const ref version will be used, and if you compare a CustomReal with a temporary, then the other version will be used. But if you have both, you should be okay.

Now, why

assert(a == CustomReal(123.123456));

works, and

assert(a == Create());  

doesn't, I'm not sure. I'd actually expect both of them to fail, given that const ref can't take a temporary, but for some reason, the compiler accepts it here - it probably has to do with how it treats opEquals special. Regardless, as I said, there are some issues with opEquals and structs which need to be ironed out, and hopefully that'll happen soon. But in the meantime, declaring both overloads of opEquals seems to do the trick.

EDIT: It appears that the reason that

assert(a == CustomReal(123.123456));

works, and

assert(a == Create());

doesn't is because of the fact (for reasons that I don't understand) a struct literal is considered an lvalue, whereas the return value of a function which is not ref is (unsurprisingly) an rvalue. There are a couple of bug reports related to it, arguing that struct literals should be rvalues, but apparently they're lvalues by design (which baffles me). In any case, that's why a function which takes const ref works with a struct literal but not with the return value of a function.

like image 87
Jonathan M Davis Avatar answered Jan 02 '23 18:01

Jonathan M Davis


#1: Yep, const ref can't bind to rvalues. Andrei thinks it was a bad idea to allow that in C++ IIRC. http://digitalmars.com/d/archives/digitalmars/D/const_ref_rvalues_103509.html#N103514

The weird thing is struct literals count as lvalues in D2, so this works:

struct A {}
void foo(ref A a) {}
void main()
{
    foo(A());
}

while calling the following doesn't:

static A bar()
{
    return A();
}

foo(bar());
like image 41
Trass3r Avatar answered Jan 02 '23 18:01

Trass3r