2010-10-09 15 views
5

¿Cuál es la mejor manera de enlazar una referencia rvalue a un objeto dado o una copia temporal del mismo?estilo al vincular una referencia a objeto-o-dummy

A &&var_or_dummy = modify? static_cast<A&&>(my_A) 
         : static_cast<A&&>(static_cast<A>(my_A)); 

(Este código no funciona en mi reciente GCC 4.6 ... Recuerdo que funcione antes, pero ahora siempre devuelve una copia.)

En la primera línea, la static_cast transforma my_A de una lvalue a un xvalor. (C++ 0x §5.2.9/1-3) El static_cast interno en la segunda línea realiza la conversión lvalue-a-rvalue, y el externo obtiene un valor x de este prvalue.

Esto parece ser compatible porque la referencia nombrada está vinculada condicionalmente al temporal según §12.2/5. El mismo truco funciona de la misma manera en C++ 03 con una referencia const.

también puedo escribir la misma cosa menos verbosidad:

A &&var_or_dummy = modify? std::move(my_A) 
         : static_cast<A&&>(A(my_A)); 

Ahora es mucho más corto. La primera abreviatura es cuestionable: se supone que move indica que algo le está sucediendo al objeto, no una mera combinación de valor lvalue-a-xvalor-a-valor. Confusamente, move no se puede usar después del : porque la llamada a la función interrumpiría el enlace temporal a referencia. La sintaxis A(my_A) es quizás más clara que la static_cast, pero es técnicamente equivalente a un modelo de estilo C.

también puedo ir hasta el final y escribir por completo en C-estilo arroja:

A &&var_or_dummy = modify? (A&&)(my_A) : (A&&)(A(my_A)); 

Después de todo, si esto va a ser un idioma, debe ser conveniente, y no es static_cast realmente protegiéndome de cualquier cosa de todos modos: el peligro real es no vincular directamente al my_A en el caso true.

Por otro lado, esto fácilmente se ve dominado por el nombre de tipo repetido tres veces. Si A fuera reemplazado por una plantilla grande y fea, realmente querría un atajo real.

(Tenga en cuenta que V se evalúa sólo una vez a pesar de aparecer cinco veces :)

#define VAR_OR_DUMMY(C, V) ((C)? \ 
    static_cast< typename std::remove_reference< decltype(V) >::type && >(V) \ 
: static_cast< typename std::remove_reference< decltype(V) >::type && > ( \ 
    static_cast< typename std::remove_reference< decltype(V) >::type >(V))) 

hacker como macros son, creo que es la mejor alternativa del montón. Es un poco peligroso porque devuelve un valor x, por lo que no debe usarse fuera de la inicialización de referencia.

Debe haber algo en lo que no haya pensado ... ¿sugerencias?

+0

Sin macro, creo que puede cortar una mención más de tipo 'A' utilizando 'auto && var_or_dummy = ...'. No es que sea mucho mejor ... Para mi edificación: 'VAR_OR_DUMMY' no se puede implementar como una plantilla de función porque el temporal debe estar vinculado directamente a la referencia rvalue y devolver una referencia rvalue de una función no funcionará, a la derecha ? –

+0

@James: ¿Puedo usar 'auto' de alguna manera en lugar de' remove_reference :: type'? ... Sí, eso es lo que pienso. Traté de hacer que devolviera una referencia lvalue también (ver el historial de edición) pero d'oh, no se permiten funciones. – Potatoswatter

+0

No, no creo que 'auto' se pueda usar en lugar de' remove_reference :: type' (aunque al buscarlo, descubrí que esto es válido: 'auto p = new auto (1);' ... 'p' tiene tipo' int * '). –

Respuesta

2

Sólo evitar todo este lío con una llamada a la función adicional:

void f(bool modify, A &obj) { 
    return [&](A &&obj) { 
    real(); 
    work(); 
    }(modify ? std::move(obj) : std::move(A(obj))); 
} 

En lugar de:

void f(bool modify, A &obj) { 
    A &&var_or_dummy = /* ??? */; 
    real(); 
    work(); 
} 

¡Es lambdas, lambdas, everywhere!

+0

¿Está seguro de que el resultado del operador condicional puede referirse alguna vez al objeto original en este caso? El segundo operando es un valor x y el tercer operando es un valor prve. E incluso si agrega un std :: move() alrededor de A (obj), esto no funcionará con GCC ya que GCC parece tener errores w.r.t. el operador condicional y los operandos xvalor. No tengo la compilación más reciente de GCC. ¿Has probado este código por casualidad? – sellibitze

+0

@sellibitze: esta es la manera canónica de usar valores x. El mismo error con '?:' Y xvalues ​​afecta mi código ... Esperaría que eso se solucionara pronto. (Y funcionó en el pasado.) – Potatoswatter

+1

+1, esta es también la manera canónica de usar expresiones lambda. Esto tendería a probar la pureza funcional de C++ 0x ... ¿Cuánta diferencia hace * realmente * que este bloque tenga su propio marco de pila? – Potatoswatter

2

Veo dos problemas con su enfoque.

que se basan en el comportamiento

int i = 0; 
int& j = true?  i :  i; 
int&& k = true? move(i) : move(i); 
assert(&i == &j); // OK, Guaranteed since C++98 
assert(&i == &k); // Does this hold as well? 

El actual proyecto de norma N3126 contiene 5.16/4:

Si el segundo y tercer operandos [al operador condicional] son ​​glvalues ​​de la misma categoría de valor y tienen el mismo tipo, el resultado es de ese tipo y valor de categoría

lo que me hace pensar que las dos afirmaciones anteriores deben mantenerse. Pero usando GCC 4.5.1 el segundo falla. Creo que esto es un error de GCC.

Además, usted confía en el compilador para extender el tiempo de vida del objeto temporal se refiere a y en el siguiente ejemplo:

A func(); 

A&& x = func();     // #1 
A&& y = static_cast<A&&>(func()); // #2 

x no va a ser una referencia colgando pero no estoy tan seguro sobre y. Creo que la regla sobre extender la vida útil de los temporales se supone que solo se aplica en los casos en que las expresiones del inicializador son puros rvalues. Al menos, esto simplificaría en gran medida la implementación. Además, GCC parece estar de acuerdo conmigo en este caso. GCC no extiende la vida útil del objeto temporal A en el segundo caso. Este sería un problema de referencia colgante en su enfoque .

Actualización: De acuerdo con 12.2/5, se supone que las vidas de los objetos temporales se extienden en ambos casos, # 1 y # 2. Ninguno de los puntos en la lista de excepciones parece aplicarse aquí. Una vez más, GCC parece tener errores en este sentido.

Una solución fácil para el problema sería:

vector<A> tempcopy; 
if (!modify) tempcopy.push_back(myA); 
A& ref = modify ? myA : tempcopy.back(); 

Alternativly, se podría utilizar un impulso :: scoped_ptr en lugar de un vector.

+0

Sí ... (En su primer ejemplo, 'static_cast' es necesario en lugar de' move', que es una función.) El FCD no diferencia entre las referencias lvalue y las referencias rvalue en la cláusula de extensión de por vida, así que estoy bastante seguro de que las referencias son similares a ese respecto. +1 para la solución ... muy mal C++ 0x no importó Boost Opcional, que es la mejor herramienta para ese trabajo. – Potatoswatter

+0

@Potatoswatter: Estoy confundido. ¿Por qué crees que hace alguna diferencia en el * primer * ejemplo si se usa move o static_cast? El primer ejemplo no trata de problemas de por vida. Se trata de si la expresión xvalue resultante realmente se refiere al objeto original. – sellibitze

+0

@sellibitze: 12.2/5. Una idea clave es que las expresiones intermedias son valores x, no referencias, por lo que el fraseo "referencia está vinculada" se refiere exclusivamente a objetos con nombre. (Y el valor de retorno de una función declarada con tipo de referencia, que es un caso excluido). – Potatoswatter

0

El problema de la seguridad de xvalue puede solucionarse en cierta medida al proporcionar una alternativa para el uso dentro de expresiones. Los temas son completamente diferentes, ahora No quieren un resultado xValue y podemos usar una función:

template< typename T > 
T &var_or_dummy(bool modify, T &var, T &&dummy = T()) { 
    if (modify) return var; 
    else return dummy = var; 
} 

    maybe_get_result(arg, var_or_dummy(want_it, var)); 

Ahora el tipo tiene que ser predeterminado Urbanizable, y el maniquí se construye siempre. La copia se evalúa condicionalmente. No creo que realmente quiera lidiar con el código que hizo demasiado de esto.

Boost Optional pueden ayudar un poco; Sólo se requiere CopyConstructible T:

template< typename T > 
T &var_or_dummy(bool modify, T &var, 
       boost::optional<T> &&dummy = boost::optional<T>()) { 
    if (modify) return var; 
    else return dummy = var; 
} 

opcional es útil, pero tiene cierta superposición con las uniones C++ 0x. No es demasiado difícil de reimplementar.

template< class T > 
struct optional_union { 
    bool valid; 
    union storage { 
     T obj; // union of one non-POD member simply reserves storage 

     storage() {} // uh, what could the constructor/destructor possibly do?? 
     ~storage() {} 
    } s; 

    optional_union() : valid(false) {} 
    optional_union &operator=(T const &in) { 
     new(&s.obj) T(in); // precondition: ! valid 
     valid = true; 
     return *this; 
    } 
    ~optional_union() 
     { if (valid) s.obj.~T(); } 
}; 

template< typename T > 
T &var_or_dummy(bool modify, T &var, 
       optional_union<T> &&dummy = optional_union<T>()) { 
    if (modify) return var; 
    else return (dummy = var).s.obj; 
} 

La clase optional_union sólo es suficiente para esta aplicación ... es obvio que se podría ampliar mucho.

+1

Como ya has puesto "el trabajo" en una función separada para tu primer ejemplo, podrías escribir 'if (modify) {work (myA); } else {A copy = myA; trabajo (copia); } ';-) – sellibitze

+0

@sellibitze: El objetivo es evitar la duplicación de código. – Potatoswatter

Cuestiones relacionadas