miércoles, 27 de abril de 2016

Rvalue y Lvalue

C++11 trajo numerosas novedades al lenguaje. Esta entrada la voy a dedicar a explicar la diferencia entre estos dos conceptos, pues son ampliamente utilizados desde la aparición de dicho estándar.

C++ entiende como Lvalue todo aquel elemento que puede ser modificado o que sobrevive a una instrucción mientras que etiquetará como Rvalue el resto de elementos.

¿Qué se supone que significa la frase anterior?

Antes de llegar a los ejempos que terminen de aclarar estos conceptos es importante indicar de dónde vienen estos nombres, ya que suponen toda una declaración de intenciones:
  • Rvalue viene de Right value, es decir, el valor de la derecha.
  • Lvalue significa Left value o valor de la izquierda.
Lo anterior viene a significar que normalmente los Lvalue se encontrarán en la parte izquierda de una expresión y los Rvalue a la derecha.

Veamoslo con ejempos. Imaginemos una sentencia típica de C++:

int x = 4;

En este caso x es un elemento que es modificable y que además va a sobrevivir a esa expresión. Después de esta instrucción podremos seguir usando x para nuestros propósitos. x será por tanto un Lvalue.

Por otro lado, el valor 4 es un literal y, por tanto, no puede ser modificado bajo ningún concepto. Además el literal no existirá una vez se haya ejecutado esa instrucción, pues no podemos referenciarlo de ninguna forma. El literal es un Rvalue.

¿Qué quiere decir que no podemos referenciarlo?

Significa literalmente eso, que su valor no puede ser asignado a una referencia:
// error: invalid initialization of non-const reference of type
// 'int&' from an rvalue of type 'int'
int &x = 2; 

Veamos otro ejemplo:

const int x = func();
int y = x; 
int z = x + y;
x = z;

En la primera línea tenemos un Lvalue que es la variable x y un Rvalue que es func(). Hay que destacar que tras esta instrucción, x pasará a ser Rvalue tras esta instrucción ya que está etiquetada como constante.

En la segunda línea tenemos a y, que es un Lvalue y a x que, como hemos comentado, es un Rvalue.

En la tercera línea tenemos dos Lvalue: y y z. La diferencia entre ambos es que y actuará de forma temporal como un Rvalue. Dado que un Rvalue es mucho más restrictivo que un Lvalue la conversión del segundo al primero esta permitida.

En la cuarta instrucción estamos intentando modificar el valor de un Rvalue, lo cual no está permitido. El compilador mostrará un error en esta línea.

Una forma rápida de identificar algunos Rvalue pasa por seguir la siguiente norma: "Si la expresión no tiene nombre, entonces es un Rvalue":

int x, y z;

// Error: x+1 es una expresión que no tiene nombre
x + 1 = z;

// Error: la expresión entre paréntesis tampoco tiene nombre
((x>3)? y : z) = 10;

// Error: 10 no tiene nombre, es un literal
10 = x + y;

// Error: la expresión static_cast hace que x se comporte como
// un Rvalue
static_cast<unsigned int>(x) = 5;

No hay comentarios:

Publicar un comentario