2010-10-27 28 views
8

Dada GMan's función de utilidad auto_cast deliciosamente mal inventado here, he estado tratando de averiguar por qué no compila para mí cuando estoy tratando de auto_cast de un valor de lado derecho (en MSVC 10.0).C++ 0x rvalue argumento de plantilla de referencia deducción

Aquí está el código que estoy usando:

template <typename T> 
class auto_cast_wrapper : boost::noncopyable 
{ 
    public: 
    template <typename R> 
    friend auto_cast_wrapper<R> auto_cast(R&& pX); 

    template <typename U> 
    operator U() const 
    { 
     return static_cast<U>(std::forward<T>(mX)); 
    } 

    private: 
    //error C2440: 'initializing': cannot convert from 'float' to 'float &&' 
    auto_cast_wrapper(T&& pX) : mX(pX) { } 

    T&& mX; 
}; 

template <typename R> 
auto_cast_wrapper<R> auto_cast(R&& pX) 
{ 
    return auto_cast_wrapper<R>(std::forward<R>(pX)); 
} 

int main() 
{ 
    int c = auto_cast(5.0f); // from an rvalue 
} 

A lo mejor de mi capacidad he tratado de seguir la referencia C++ 0x colapso de las reglas y las reglas de deducción de argumento de plantilla descrito here, y por lo que puedo decir, el código dado arriba debería funcionar.

Recordemos que en la pre-0x C++, no se le permite tomar una referencia a una referencia: algo así como un & & provoca un error de compilación. C++ 0x, por el contrario, introduce las siguientes reglas colapso de referencia:

  • A & & se convierte en un &
  • A & & & se convierte en un &
  • A & & & se convierte en un &
  • A & & & & se convierte en un & &

La segunda regla es una regla especial deducción argumento de plantilla para plantillas de función que tienen un argumento por referencia rvalue a un argumento de plantilla:

template<typename T> 
void foo(T&&); 

En este caso, se aplican las siguientes reglas :

  1. Cuando foo se llama en una lvalue de tipo a, entonces T se resuelve en un & y por lo tanto, por el refere A pesar de las reglas de colapso anteriores, el tipo de argumento se convierte efectivamente en A &.
  2. Cuando se invoca foo en un valor r de tipo A, entonces T se resuelve en A, y de ahí que el tipo de argumento se convierta en A & &.

Ahora, cuando el cursor sobre la llamada a auto_cast(5.0f), la información sobre herramientas muestra correctamente su valor de retorno como auto_cast_wrapper<float>. Este sentido de que el compilador ha seguido correctamente la regla 2:

Cuando se llama foo en un valor p de tipo A, entonces T se resuelve a A.

lo tanto, ya tenemos un auto_cast_wrapper<float>, el constructor debe instanciar para tomar un float&&. Pero el mensaje de error parece implicar que crea un valor de float por valor.

error showing tooltip

Este es el mensaje de error completo, demostrando una vez más que T = flote correctamente sin embargo, el T & & parámetro se convierte en T?

main.cpp(17): error C2440: 'initializing' : cannot convert from 'float' to 'float &&' 
    You cannot bind an lvalue to an rvalue reference 
    main.cpp(17) : while compiling class template member function 'auto_cast_wrapper<T>::auto_cast_wrapper(T &&)' 
    with 
    [ 
     T=float 
    ] 
    main.cpp(33) : see reference to class template instantiation 'auto_cast_wrapper<T>' being compiled 
    with 
    [ 
     T=float 
    ] 

¿Alguna idea?

+2

¿Alguna posibilidad de que puedas enfocar un poco la pregunta? En lugar de poner ediciones en la parte inferior como párrafos separados, edítelos * en * la pregunta en sí. Como lector no necesito saber el orden en el que adjuntó las cosas a la pregunta, solo quiero leer la mejor versión posible de la pregunta. Y si desea publicar la respuesta usted mismo, hágalo como una respuesta real, no como un párrafo más en la parte inferior de la pregunta. Tal como están las cosas, es solo una cantidad aterradora de texto y código que tengo que revisar si quiero saber de qué se trata la pregunta. – jalf

+0

@jalf: Sí, lo siento jalf. No quería crear una respuesta separada porque DeadMG ya respondió correctamente. Supongo que pensé que sería grosero de mi parte hacer eso, aunque pensé que algunas aclaraciones no iban a salir mal para nadie más leyendo esto en el futuro. Y la pregunta extra probablemente debería ser una pregunta completamente separada, ya que no está realmente relacionada con el original. – dvide

+1

En SO, no hay nada de malo en publicar nuevas y mejores respuestas a preguntas que ya han sido respondidas. Y si te sientes mal por eso, solo acepta la respuesta de DeadMG, pero publica la tuya debajo de ella. :) – jalf

Respuesta

4

Olvidó std :: reenviar el argumento 0 & & al constructor auto_cast_wrapper. Esto rompe la cadena de envío. El compilador ahora da una advertencia pero parece funcionar bien.

template <typename T> 
class auto_cast_wrapper 
{ 
    public: 
    template <typename R> 
    friend auto_cast_wrapper<R> auto_cast(R&& pX); 

    template <typename U> 
    operator U() const 
    { 
     return static_cast<U>(std::forward<T>(mX)); 
    } 

    private: 
    //error C2440: 'initializing': cannot convert from 'float' to 'float &&' 
    auto_cast_wrapper(T&& pX) : mX(std::forward<T>(pX)) { } 

    auto_cast_wrapper(const auto_cast_wrapper&); 
    auto_cast_wrapper& operator=(const auto_cast_wrapper&); 

    T&& mX; 
}; 

template <typename R> 
auto_cast_wrapper<R> auto_cast(R&& pX) 
{ 
    return auto_cast_wrapper<R>(std::forward<R>(pX)); 
} 

float func() { 
    return 5.0f; 
} 

int main() 
{ 

    int c = auto_cast(func()); // from an rvalue 
    int cvar = auto_cast(5.0f); 

    std::cout << c << "\n" << cvar << "\n"; 
    std::cin.get(); 
} 

Imprime un par de cincos.

+0

¿Quiere decir que el constructor debe ser 'auto_cast_wrapper (T && pX): mX (std :: forward (pX))'? Creo que tienes razón en realidad, eso tiene sentido. Pero ahora creo que muestra un comportamiento indefinido. Creo que el literal queda fuera del alcance antes de que el operador de conversión pueda ser despedido. ¿Entonces parece que esto no puede funcionar de valores de todos modos? – dvide

+0

@dvide: Lo probé con algunos valores -por ejemplo, devuelto por el valor de una función- y parece funcionar. – Puppy

+0

@DeadMG: ¿Cuál es su advertencia? ¿Y en qué compilador? Recibo la advertencia: el miembro de referencia se inicializa a un temporal que no persiste después de que el constructor sale. Y cuando lo pruebo, el valor de 'c' está en mal estado. ¿Podría ser que simplemente parece funcionar, dado que técnicamente no está definido? – dvide

2

Lo siento por publicar el código no probado. :)

DeadMG es correcto que el argumento debe ser reenviado también. Creo que la advertencia es falsa y MSVC tiene un error. Consideremos de la llamada:

auto_cast(T()); // where T is some type 

T() vivirá hasta el final de la expresión completa, lo que significa que la función auto_cast, constructor de la auto_cast_wrapper 's, y la conversión definida por el usuario son todos referencia a un objeto sigue siendo válida.

(Dado que la envoltura no puede hacer otra cosa que convertir o destrucción, no puede sobrevivir al valor que fue pasado en auto_cast.)

lo arreglo podría ser la de hacer que el miembro de sólo un T. Sin embargo, harás una copia/movimiento en lugar de lanzar el objeto original directamente. Pero tal vez con la optimización del compilador desaparece.


Y no, el reenvío no es superfluo. Se mantiene la categoría de valor de lo que estamos convirtiendo automáticamente:

struct foo 
{ 
    foo(int&) { /* lvalue */ } 
    foo(int&&) { /* rvalue */ } 
}; 

int x = 5; 
foo f = auto_cast(x); // lvalue 
foo g = auto_cast(7); // rvalue 

Y si no me equivoco, el operador de conversión no debe ser (sin duda no tiene que ser) marcó const.

+0

Gracias GMan. No sabía que debería durar hasta el final de toda la expresión. Eso tiene sentido. Supongo que esto es solo un error entonces. Y gracias por la perfecta aclaración de reenvío. Eso es muy bonito. – dvide

+0

@dvide: Sin problemas. No cometer el 100% de que es un error, pero estoy bastante seguro. Sigue siendo solo una referencia (como la versión 'const &'), solo puede ser un lvalue o un valor r, por lo que creo que debería ser el mismo. Gracias por echarle un vistazo. – GManNickG

+0

Sí, estaba un poco confundido con la incongruencia de por vida del temporal entre las dos versiones. Por lo que pude decir, deberían ser lo mismo, pero todavía me estoy acostumbrando a validar las referencias, así que soy muy cauteloso de sacar conclusiones precipitadas =) – dvide

2

La razón de que no se compila es la misma razón de por qué esto no se compila:

float rvalue() { return 5.0f } 

float&& a = rvalue(); 
float&& b = a; // error C2440: 'initializing' : cannot convert from 'float' to 'float &&' 

Como a es en sí misma un valor-I no puede ser obligado a b. En el constructor auto_cast_wrapper deberíamos haber usado std::forward<T> en el argumento nuevamente para solucionar esto. Tenga en cuenta que solo podemos usar std::move(a) en el ejemplo específico anterior, pero esto no cubriría el código genérico que debería funcionar también con lvalues. Por lo que el constructor auto_cast_wrapper ahora se convierte en:

template <typename T> 
class auto_cast_wrapper : boost::noncopyable 
{ 
    public: 
    ... 

    private: 
    auto_cast_wrapper(T&& pX) : mX(std::forward<T>(pX)) { } 

    T&& mX; 
}; 

Por desgracia, parece que esto ahora exhibe un comportamiento indefinido.Me da la siguiente advertencia:

C4413 advertencia: 'auto_cast_wrapper :: MX': miembro de referencia se inicializa a un temporal que no persisten después del constructor sale de

Parece que va el literal fuera del alcance antes de que el operador de conversión pueda ser despedido. Aunque esto podría ser solo un error del compilador con MSVC 10.0. Desde GMan's answer, la duración de un temporal debe vivir hasta el final de la expresión completa.

Cuestiones relacionadas