¿Cómo es que una referencia no constante no puede vincularse a un objeto temporal?

¿Por qué no se permite obtener una referencia no constante a un objeto temporal, que getx() función getx() ? Claramente, esto está prohibido por C ++ Standard, pero estoy interesado en el propósito de tal restricción, no en una referencia al estándar.

 struct X { X& ref() { return *this; } }; X getx() { return X();} void g(X & x) {} int f() { const X& x = getx(); // OK X& x = getx(); // error X& x = getx().ref(); // OK g(getx()); //error g(getx().ref()); //OK return 0; } 
  1. Está claro que la vida del objeto no puede ser la causa, porque la referencia constante a un objeto no está prohibida por C ++ Standard.
  2. Está claro que el objeto temporal no es constante en la muestra anterior, porque se permiten llamadas a funciones no constantes. Por ejemplo, ref() podría modificar el objeto temporal.
  3. Además, ref() permite engañar al comstackdor y obtener un enlace a este objeto temporal y eso resuelve nuestro problema.

En adición:

Dicen que “la asignación de un objeto temporal a la referencia constante amplía la vida útil de este objeto” y “No se dice nada acerca de referencias no constantes”. Mi pregunta adicional ¿La siguiente asignación extiende la vida útil del objeto temporal?

 X& x = getx().ref(); // OK 

De este artículo del blog de Visual C ++ sobre las referencias de rvalue :

… C ++ no quiere que modifiques temporalmente las temporarias, pero llamar directamente a una función miembro no const en un valor r modificable es explícito, por lo que está permitido …

Básicamente, no debes tratar de modificar los temporales por el solo hecho de que son objetos temporales y morirán en cualquier momento. La razón por la que se te permite llamar a métodos no const es que, bueno, puedes hacer algunas cosas “estúpidas” siempre y cuando sepas lo que estás haciendo y seas explícito al respecto (por ejemplo, usando reinterpret_cast). Pero si enlazas una referencia temporal a una referencia no constante, puedes seguir pasándola “para siempre” solo para que desaparezca tu manipulación del objeto, porque en algún momento olvidaste por completo que era temporal.

Si yo fuera tú, reconsideraría el diseño de mis funciones. ¿Por qué g () acepta la referencia, modifica el parámetro? Si no, hazlo const referencia, si es así, ¿por qué tratas de pasarlo de manera temporal, no te importa que es un temporal que estás modificando? ¿Por qué getx () vuelve temporal de todos modos? Si comparte con nosotros su escenario real y lo que está tratando de lograr, puede obtener algunas buenas sugerencias sobre cómo hacerlo.

Ir contra el lenguaje y engañar al comstackdor raramente resuelve problemas, generalmente crea problemas.


Editar: Responder preguntas en el comentario: 1) X& x = getx().ref(); // OK when will x die? X& x = getx().ref(); // OK when will x die? – No sé y no me importa, porque esto es exactamente lo que quiero decir con “ir contra el lenguaje”. El lenguaje dice “los temporales mueren al final del enunciado, a menos que estén obligados a hacer referencia, en cuyo caso mueren cuando la referencia sale del scope”. Al aplicar esa regla, parece que x ya está muerto al comienzo de la siguiente instrucción, ya que no está obligado a referencia constante (el comstackdor no sabe qué devuelve ref). Sin embargo, esto es solo una suposición.

2) Declaré claramente el propósito: no está permitido modificar los temporales, porque simplemente no tiene sentido (ignorando las referencias de C ++ 0x rvalue). La pregunta “¿por qué puedo llamar a miembros que no son const?” es bueno, pero no tengo una respuesta mejor que la que ya mencioné anteriormente.

3) Bueno, si estoy en lo cierto acerca de x en X& x = getx().ref(); muriendo al final de la statement, los problemas son obvios.

De todos modos, en función de sus preguntas y comentarios, no creo que ni siquiera estas respuestas adicionales lo satisfagan. Aquí hay un último bash / resumen: el comité de C ++ decidió que no tiene sentido modificar los temporales, por lo tanto, no permitieron el enlace a referencias no constantes. Puede que haya alguna implementación del comstackdor o problemas históricos que también estuvieron involucrados, no sé. Luego, surgió un caso específico, y se decidió que, a pesar de todo, todavía permitirían la modificación directa mediante el método de llamadas sin const. Pero esa es una excepción: por lo general, no está permitido modificar los temporales. Sí, C ++ es a menudo tan raro.

En su código, getx() devuelve un objeto temporal, el llamado “valorR”. Puede copiar rvalues ​​en objetos (también conocidas como variables) o vincularlos a referencias const (que ampliarán su tiempo de vida hasta el final de la vida de la referencia). No puede vincular valores r a referencias no const.

Esta fue una decisión de diseño deliberada para evitar que los usuarios modifiquen accidentalmente un objeto que va a morir al final de la expresión:

 g(getx()); // g() would modify an object without anyone being able to observe 

Si desea hacer esto, deberá hacer una copia local o del objeto primero o vincularlo a una referencia constante:

 X x1 = getx(); const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference g(x1); // fine g(x2); // can't bind a const reference to a non-const reference 

Tenga en cuenta que el próximo estándar de C ++ incluirá referencias rvalue. Por lo tanto, lo que usted conoce como referencias se va a llamar “referencias lvalue”. Se le permitirá vincular valores para validar referencias y puede sobrecargar funciones en “rvalue-ness”:

 void g(X&); // #1, takes an ordinary (lvalue) reference void g(X&&); // #2, takes an rvalue reference X x; g(x); // calls #1 g(getx()); // calls #2 g(X()); // calls #2, too 

La idea detrás de las referencias de valor es que, dado que estos objetos van a morir de todos modos, puedes aprovechar ese conocimiento e implementar lo que se llama “semántica de movimiento”, un cierto tipo de optimización:

 class X { X(X&& rhs) : pimpl( rhs.pimpl ) // steal rhs' data... { rhs.pimpl = NULL; // ...and leave it empty, but deconstructible } data* pimpl; // you would use a smart ptr, of course }; X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty 

Lo que está mostrando es que se permite el encadenamiento del operador.

  X& x = getx().ref(); // OK 

La expresión es ‘getx (). Ref ();’ y esto se ejecuta hasta su finalización antes de su asignación a ‘x’.

Tenga en cuenta que getx () no devuelve una referencia sino un objeto completamente formado en el contexto local. El objeto es temporal pero no es const, lo que le permite llamar a otros métodos para calcular un valor o tener otros efectos secundarios.

 // It would allow things like this. getPipeline().procInstr(1).procInstr(2).procInstr(3); // or more commonly std::cout < < getManiplator() << 5; 

Mira el final de esta respuesta para un mejor ejemplo de esto

No puede vincular un carácter temporal a una referencia porque al hacerlo generará una referencia a un objeto que se destruirá al final de la expresión, dejándole con una referencia colgante (que es desordenada y al patrón no le gusta desordenado).

El valor devuelto por ref () es una referencia válida, pero el método no presta atención a la vida útil del objeto que está devolviendo (porque no puede tener esa información dentro de su contexto). Básicamente acabas de hacer el equivalente de:

 x& = const_cast(getX()); 

La razón por la que está bien hacer esto con una referencia constante a un objeto temporal es que el estándar amplía la vida útil del temporal a la vida útil de la referencia, por lo que la vida útil de los objetos temporales se extiende más allá del final del enunciado.

Entonces, la única pregunta que queda es por qué la norma no quiere permitir que la referencia a los temporales prolongue la vida del objeto más allá del final de la statement.

Creo que es porque hacerlo haría que el comstackdor sea muy difícil de corregir para objetos temporales. Se hizo para referencias constantes a temporales, ya que esto tiene un uso limitado y, por lo tanto, te obligó a hacer una copia del objeto para hacer algo útil, pero proporciona algunas funcionalidades limitadas.

Piensa en esta situación:

 int getI() { return 5;} int x& = getI(); x++; // Note x is an alias to a variable. What variable are you updating. 

Extender la vida útil de este objeto temporal va a ser muy confuso.
Mientras que el siguiente:

 int const& y = getI(); 

Le dará un código que es intuitivo de usar y comprender.

Si desea modificar el valor, debe devolver el valor a una variable. Si está tratando de evitar el costo de copiar el objeto desde la función (ya que parece que el objeto está copiado de nuevo (técnicamente lo es)). Entonces no te molestes, el comstackdor es muy bueno en 'Return Value Optimization'

¿Por qué se discute en las preguntas frecuentes de C ++ (mina en negrita ):

En C ++, las referencias no const pueden vincularse a lvalues ​​y las referencias const pueden vincularse a lvalues ​​o rvalues, pero no hay nada que pueda vincularse a un valor r no-const. Eso es para proteger a las personas de cambiar los valores de los temporales que se destruyen antes de que se pueda usar su nuevo valor . Por ejemplo:

 void incr(int& a) { ++a; } int i = 0; incr(i); // i becomes 1 incr(0); // error: 0 is not an lvalue 

Si se permitiera ese incr (0), o bien se incrementaría algo temporal que nadie más vería, o – mucho peor – el valor de 0 se convertiría en 1. Esto último suena tonto, pero en realidad había un error así en los primeros comstackdores de Fortran que establecían a un lado una ubicación de memoria para mantener el valor 0.

El problema principal es que

 g(getx()); //error 

es un error lógico: g está modificando el resultado de getx() pero no tiene ninguna posibilidad de examinar el objeto modificado. Si g no necesita modificar su parámetro, entonces no habría requerido una referencia de valor l, podría haber tomado el parámetro por valor o por referencia constante.

 const X& x = getx(); // OK 

es válido porque a veces necesita reutilizar el resultado de una expresión, y está bastante claro que está tratando con un objeto temporal.

Sin embargo, no es posible hacer

 X& x = getx(); // error 

válido sin hacer que g(getx()) válido, que es lo que los diseñadores de idiomas intentaban evitar en primer lugar.

 g(getx().ref()); //OK 

es válido porque los métodos solo saben acerca de la const-ness de this , no saben si se invocan en un valor l o en un valor r.

Como siempre en C ++, tiene una solución alternativa para esta regla, pero debe indicar al comstackdor que sabe lo que está haciendo al ser explícito:

 g(const_cast(getX())); 

Parece que la pregunta original sobre por qué esto no está permitido se ha respondido claramente: “porque lo más probable es que sea un error”.

FWIW, pensé que mostraría cómo hacerlo, aunque no creo que sea una buena técnica.

La razón por la que a veces quiero pasar un método temporal a un método que toma una referencia no constante es tirar intencionalmente un valor devuelto por referencia que no le importa al método de llamada. Algo como esto:

 // Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr); string name; person.GetNameAndAddr(name, string()); // don't care about addr 

Como se explicó en las respuestas anteriores, eso no se comstack. Pero esto comstack y funciona correctamente (con mi comstackdor):

 person.GetNameAndAddr(name, const_cast(static_cast(string()))); 

Esto solo muestra que puedes usar el casting para mentirle al comstackdor. Obviamente, sería mucho más limpio declarar y pasar una variable automática no utilizada:

 string name; string unused; person.GetNameAndAddr(name, unused); // don't care about addr 

Esta técnica introduce una variable local innecesaria en el scope del método. Si por algún motivo desea evitar que se use más adelante en el método, por ejemplo, para evitar confusiones o errores, puede ocultarlo en un bloque local:

 string name; { string unused; person.GetNameAndAddr(name, unused); // don't care about addr } 

– Chris

¿Por qué querrías alguna vez X& x = getx(); ? Simplemente use X x = getx(); y confíe en RVO.

La solución malvada implica la palabra clave ‘mutable’. En realidad, ser malvado se deja como un ejercicio para el lector. O mira aquí: http://www.ddj.com/cpp/184403758

Excelente pregunta, y aquí está mi bash de una respuesta más concisa (ya que mucha información útil está en los comentarios y es difícil desenterrar el ruido).

Cualquier referencia ligada directamente a un temporal extenderá su vida útil [12.2.5]. Por otro lado, una referencia inicializada con otra referencia no lo hará (incluso si finalmente es la misma temporal). Eso tiene sentido (el comstackdor no sabe a qué se refiere en última instancia).

Pero esta idea es extremadamente confusa. Por ejemplo, const X &x = X(); hará que el temporal dure tanto como la referencia x , pero const X &x = X().ref(); NO (quién sabe qué ref() realmente regresó). En este último caso, se llama al destructor para X al final de esta línea. (Esto es observable con un destructor no trivial).

Por lo tanto, parece generalmente confuso y peligroso (¿por qué complicar las reglas sobre la duración de los objetos?), Pero presumiblemente había una necesidad al menos para las referencias de const, por lo que el estándar establece este comportamiento para ellos.

[Comentario de sbi ]: tenga en cuenta que el hecho de vincularlo a una referencia de referencia mejora la duración de un ciclo temporal es una excepción que se ha agregado deliberadamente (TTBOMK para permitir optimizaciones manuales). No se agregó una excepción para las referencias no const, porque se consideró que vincular una referencia temporal a una no const probablemente sea un error del progtwigdor.

Todos los temporales persisten hasta el final de la expresión completa. Para hacer uso de ellos, sin embargo, necesitas un truco como el que tienes con ref() . Eso es legal No parece haber una buena razón para que salte el aro adicional, excepto para recordarle al progtwigdor que algo inusual está sucediendo (es decir, un parámetro de referencia cuyas modificaciones se perderán rápidamente).

[Otro comentario de sbi ] La razón por la que Stroustrup da (en D y E) por no permitir la vinculación de los valores r a las referencias no const es que, si el g () de Alexey modificaría el objeto (lo que esperarías de una función tomando un no const referencia), modificaría un objeto que va a morir, por lo que nadie podría obtener el valor modificado de todos modos. Él dice que esto, muy probablemente, es un error.

“Está claro que el objeto temporal no es constante en la muestra anterior, porque las llamadas a funciones no constantes están permitidas. Por ejemplo, ref () podría modificar el objeto temporal”.

En su ejemplo, getX () no devuelve una const X, por lo que puede llamar a ref () casi de la misma manera en que podría llamar a X (). Ref (). Usted está devolviendo una referencia no constante y por lo tanto puede llamar a métodos no const, lo que no puede hacer es asignar el ref a una referencia no const.

Junto con el comentario de SadSidos, esto hace que tus tres puntos sean incorrectos.

Tengo un escenario que me gustaría compartir en el que me gustaría poder hacer lo que Alexey está pidiendo. En un complemento Maya C ++, tengo que hacer el siguiente shenanigan para obtener un valor en un atributo de nodo:

 MFnDoubleArrayData myArrayData; MObject myArrayObj = myArrayData.create(myArray); MPlug myPlug = myNode.findPlug(attributeName); myPlug.setValue(myArrayObj); 

Esto es tedioso de escribir, así que escribí las siguientes funciones de ayuda:

 MPlug operator | (MFnDependencyNode& node, MObject& attribute){ MStatus status; MPlug returnValue = node.findPlug(attribute, &status); return returnValue; } void operator < < (MPlug& plug, MDoubleArray& doubleArray){ MStatus status; MFnDoubleArrayData doubleArrayData; MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status); status = plug.setValue(doubleArrayObject); } 

Y ahora puedo escribir el código desde el comienzo de la publicación como:

 (myNode | attributeName) < < myArray; 

El problema es que no se comstack fuera de Visual C ++, porque está intentando enlazar la variable temporal devuelta desde | operador a la referencia MPlug del operador < <. Me gustaría que fuera una referencia porque este código se llama muchas veces y prefiero no copiar tanto MPlug. Solo necesito el objeto temporal para vivir hasta el final de la segunda función.

Bueno, este es mi escenario. Solo pensé que mostraría un ejemplo donde a uno le gustaría hacer lo que Alexey describe. ¡Acepto todas las críticas y sugerencias!

Gracias.