18

Creo que la expresión T() crea un valor r (según el Estándar). Sin embargo, el código siguiente se compila (al menos en gcc4.0):¿Por qué se permite T() = T()?

class T {}; 

int main() 
{ 
    T() = T(); 
} 

sé técnicamente esto es posible debido a que las funciones miembro pueden ser invocadas en los temporales y lo anterior es sólo invocar el operador = en la rvalue temporal creada desde el primer T().

Pero, conceptualmente, esto es como asignar un nuevo valor a un valor r. ¿Hay una buena razón por la cual esto está permitido?

Edit: La razón por la que considero que esto es extraño es que está estrictamente prohibido en los tipos incorporados pero permitido en los tipos definidos por el usuario. Por ejemplo, int(2) = int(3) no compilará porque es un "lvalue inválido en la asignación".

Así que supongo que la verdadera pregunta es, ¿fue este comportamiento algo inconsistente incorporado en el lenguaje por algún motivo? ¿O está allí por alguna razón histórica? (Por ejemplo, sería conceptualmente más sólido permitir que solo se invoquen las funciones miembro miembro en las expresiones rvalue, pero eso no se puede hacer porque eso podría romper algún código existente.)

Respuesta

3

Esta es la razón por varias las clases en la biblioteca estándar pueden implementarse. Consideremos, por ejemplo std::bitset<>::operator[]

// bit reference: 
class reference { 
    friend class bitset; 
    reference(); 
public: 
    ˜reference(); 
    reference& operator=(bool x);   // for b[i] = x; 
    reference& operator=(const reference&); // for b[i] = b[j]; 
    bool operator˜() const; // flips the bit 
    operator bool() const; // for x = b[i]; 
    reference& flip();  // for b[i].flip(); 
}; 

reference operator[](size_t pos); // for b[i]; 

Si lo hace bits[i] = true se asigna exactamente un valor a un valor p de tipo de clase. El proxy devuelto por operator[] puede acceder a los bits que se empaquetan de manera eficiente en enteros.

+0

Esto junto con la respuesta de FredOverflow responde a mi pregunta por completo. ¡Gracias! – Rimo

13

Esto está permitido solo por la sobrecarga del operador, y Posibilidad de sobrecargar el operator = para hacer algo más elegante, como imprimir en la consola, bloquear un mutex, o cualquier cosa realmente.

+16

Y no solo operator = sino también los constructores. Hay muchas construcciones extrañas en C++ donde el compilador simplemente se encoge de hombros y espera que sepas lo que estás haciendo. :-) –

7

Sí, está asignando un nuevo valor a un valor r. Más precisamente, está llamando a la función miembro operator = en un valor r. Como no está utilizando el operador de asignación integrado, ¿por qué cree que esto debería ser un problema? operator = es una función miembro de la clase, que en muchos aspectos es similar a cualquier otra función miembro de la clase, incluido el hecho de que se puede invocar a los valores.

Probablemente también deba tener en cuenta el hecho de que "ser un valor r" es una propiedad de una expresión, no una propiedad de un objeto. Es cierto que la expresión T() se evalúa como un valor r. Sin embargo, el objeto temporal que produce la expresión T() sigue siendo un objeto , al que también se puede acceder como un lvalue. Por ejemplo, alguna otra función miembro puede ser llamado en el resultado de la asignación, y va a ver el "nuevo" valor (recién asignado) del objeto temporal a través de *this lvalue

(T() = T()).some_member_function(); 

También puede extender el tiempo de vida del temporal adjuntando una referencia de referencia const T& r = T() = T(); y el valor visto a través de r será el "nuevo" valor del objeto. Como Johannes señaló correctamente en su comentario, esto no lo adjuntará a un temporal.

+0

Sí, creo que entiendo el mecanismo de 'cómo' funcionan tales operaciones. Pero todavía tengo curiosidad por '¿por qué' los diseñadores de lenguaje permitieron que los valores r (o los temporales creados a partir de expresiones de valor r) se mudaran como tales? ¿Es eso un descuido de su lado o fue permitido intencionalmente (tal vez hubo algunas razones prácticas que fueron lo suficientemente convincentes como para justificar un comportamiento aparentemente inconsistente entre tipos incorporados y tipos definidos por el usuario?) – Rimo

+0

Solo para aclarar, personalmente lo haría resulta mucho menos sorprendente si solo se permitiera invocar a las funciones de miembro constante en expresiones rvalue. – Rimo

+3

@Rhimo: Eso derrota construcciones razonables como 'Atomic (std :: cout) << 1 <<" salida ininterrumpida "<< std :: endl;' – MSalters

4

Desde un Punto de vista, es inconsistente, pero pasando por alto la forma en que es consistente: 1) enteros y otros tipos incorporados todavía se comportan como lo hacen en C, 2) = operador en la clase de tipo se comporta como cualquier otro método lo hace sin requerir otro caso especial.

La compatibilidad con C ha sido muy valorada desde el comienzo de C++, y posiblemente C++ no estaría aquí hoy sin ella. Entonces esa parte es generalmente una buena cosa.

El segundo punto está subestimado. No operador especial de mayúsculas/minúsculas = permite que el código sin sentido "funcione", pero ¿por qué nos importa el código sin sentido en primer lugar? Basura dentro basura fuera.Las reglas actuales le dan un significado definido (UB aquí sería malo) con un costo insignificante, por lo que he visto.

Dado mi consumo, las cosas se simplificarían aún más, por lo que int() = int() estaría permitido. C++ 0x empieza a ir en esa dirección con rvalue referencias, prvalues, etc.

+0

"_C++ 0x comienza a dirigirse en esa dirección con rvalue-references, prvalues, etc._" ¿Abrazo? – curiousguy

5

Puede restringir el operador = a trabajar sólo en lvalues ​​en C++ 0x:

class T 
{ 
public: 
    T& operator=(const T&) & = default; 
}; 
Cuestiones relacionadas