No se puede pasar un objeto temporal como referencia
Este es un ejemplo mínimo:
class Foo
{
public:
Foo(int x) {};
};
void ProcessFoo(Foo& foo)
{
}
int main()
{
ProcessFoo(Foo(42));
return 0;
}
Lo anterior se compila bien en Visual Studio, pero genera un error en Linux y Mac.
Compilar lo anterior genera esto:
$ g++ -std=c++11 -c newfile.cpp
newfile.cpp: In function ‘int main()’:
newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
ProcessFoo(Foo(42));
^
newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
void ProcessFoo(Foo& foo)
Encontré tres soluciones:
- Cree una variable temporal para la invocación de ProcessFoo.
Como esto:
Foo foo42(42);
ProcessFoo(foo42);
ProcessFoo toma una referencia constante:
void ProcessFoo(const Foo& foo)
ProcessFoo simplemente permite que Foo pase por valor.
void ProcessFoo(Foo foo)
¿Por qué el compilador prohíbe mi código original? (¿Contra qué se protege)? ¿Qué tiene cada una de las tres soluciones anteriores que satisface al compilador? ¿Qué le permitiría MSVC, pero no g++?
Por diseño, C++ solo permite pasar un temporal a una referencia constante, valor o referencia rvalue. La idea es que una función que toma un parámetro de referencia no constante indica que desea modificar el parámetro y le permite volver a la persona que llama. Hacerlo con un temporal no tiene sentido y probablemente sea un error.
Y no sé qué versión de g++ estás ejecutando. No funciona aquí: http://coliru.stacked-crooked.com/a/43096cb398cbc973
¿Por qué el compilador prohíbe mi código original?
Porque está prohibido por la Norma:
8.5.3 Referencias 5
...
De lo contrario, la referencia será una referencia lvalue a un tipo constante no volátil (es decir, cv1 será constante), o la referencia será una referencia rvalue.
[ Ejemplo:
doble& rd2 = 2,0; // error: no es un valor l y la referencia no es constante
...
'
¿Contra qué se protege?
Modificar inadvertidamente un objeto que va a ser destruido después de la llamada a la función.
¿Qué tiene cada una de las tres soluciones anteriores que satisface al compilador?
1
Crea un objeto con nombre y 3
una copia.
2
Funciona porque la vida útil del objeto simplemente se extiende y al mismo tiempo se evitan cambios en él.
¿Qué le permitiría MSVC, pero no g++?
Porque es una extensión del idioma. Deshabilítelo yendo a Property Pages->C/C++->Language->Disable Language Extensions
y obtendrá un error.
¿Por qué el compilador prohíbe mi código original?
MSVC tiene una extensión que permite que los temporales se vinculen a referencias de valores l no constantes. Por supuesto, esta no es una característica estándar, por lo que me mantendría alejada de ella para que sea portátil. Por ejemplo, no funciona con las últimas versiones de GCC y Clang como has visto.
¿Qué tiene cada una de las tres soluciones anteriores que satisface al compilador?
En C++03, las expresiones solo podían ser valores l o valores r. Las referencias sólo podían designar el "valor" de un objeto, por lo que se usaban con la intención de asignar un alias a un objeto preexistente. Por el contrario, los valores no existen más allá de la expresión en la que aparecen. Además, el resultado final de las referencias normalmente era copiar o modificar el objeto, y no tiene mucho sentido para el lenguaje modificar un valor como 55
por ejemplo.
Las reglas le permiten vincular un valor r a una referencia de valor l a constante, en cuyo caso la vida útil del temporal se extiende a la vida útil de la referencia. Cuando tomas un objeto por valor, el objeto se copia.
Con C++ 11 tenemos rvalue-references y xvalues que se crearon con el fin de intercambiar propiedad. Con esto se reduce la utilidad de lvalue-references a const. Además, tomar por valor provoca un movimiento si es un valor.
Una vez que declaraste el prototipo de ProcessFoo como
void ProcessFoo(Foo& foo)
Está transmitiendo su intención ya que el parámetro formal "foo" está sujeto a modificación ya que const & no lo pasa.
En el lugar de la llamada,
ProcessFoo(Foo(42));
Foo(42) está creando un objeto de pila temporal que no se puede modificar. Está bien pasar por valor o pasar por referencia a constante a un método.
Como usted mismo enumeró, satisfacer esas restricciones hace feliz al compilador.
- le está dando un objeto que no es generado por el compilador y está bajo su control.
- Informa al compilador que el método garantiza la constancia del objeto.
- Informa al compilador que este objeto (temporal) se pasa por valor y, por lo tanto, no hay problemas.