2010-10-27 18 views
8

Dato GMan's deliziosamente malvagio auto_cast funzione di utilità inventata here, Ho cercato di capire perché non viene compilato per me quando sto provando a auto_cast da un valore di rvalue (su MSVC 10.0).C++ 0x valore di riferimento argomento argomento deduzione

Ecco il codice che sto 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 
} 

Al meglio delle mie possibilità ho cercato di seguire il riferimento C++ 0x collasso regole e le regole di deduzione modello di argomenti delineati here, e per quanto posso dire il codice di cui sopra dovrebbe funzionare.

Ricordiamo che in pre-0x C++, non è consentito di prendere un riferimento a un riferimento: qualcosa come un & & causa un errore di compilazione. C++ 0x, invece, presenta le seguenti regole collasso riferimento:

  • Un & & diventa un &
  • Un & & & diventa un &
  • Un & & & diventa un &
  • A & & & & diventa un & &

La seconda regola è una speciale regola detrazione modello argomento per i modelli funzionali che accettano un argomento con riferimento rvalue ad un argomento di un template:

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

Qui, si applicano le seguenti regole :

  1. Quando foo è chiamato su un lvalue di tipo a, allora T risolve in un & e quindi del RIFERIME nce comprime le regole sopra, il tipo di argomento diventa effettivamente A &.
  2. Quando foo viene chiamato su un valore rvalore di tipo A, quindi T si risolve in A, quindi il tipo di argomento diventa A & &.

Ora, quando il mouse sopra la chiamata a auto_cast(5.0f), il tooltip visualizza correttamente il suo valore di ritorno come auto_cast_wrapper<float>. Questo senso che il compilatore ha seguito correttamente Regola 2:

Quando foo è chiamata su un rvalue di tipo A, allora T decide di A.

Quindi, dal momento che abbiamo un auto_cast_wrapper<float>, il costruttore dovrebbe istanziare per prendere un float&&. Ma il messaggio di errore sembra implicare che esso istanzia per prendere un valore float.

error showing tooltip

Ecco il messaggio di errore completo, che mostra ancora una volta che T = galleggiano correttamente ancora il T & & parametro diventa 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 
    ] 

Qualche idea?

+2

Qualche possibilità di mettere a fuoco la domanda un po '? Invece di mettere le modifiche in fondo come paragrafi separati, modificale * nella * domanda stessa. Come lettore non ho bisogno di sapere l'ordine in cui hai aggiunto alla domanda, voglio solo leggere la migliore versione possibile della domanda. E se vuoi pubblicare la risposta da solo, fallo come una risposta reale, non come un altro paragrafo in fondo alla domanda. Così com'è, è solo una quantità spaventosa di testo e codice che devo attraversare se voglio anche sapere di che cosa si tratta. – jalf

+0

@jalf: Sì, mi dispiace jalf. Non volevo creare una risposta separata perché DeadMG ha già risposto correttamente. Immagino di aver pensato che sarebbe stato maleducato da parte mia farlo, anche se ho pensato che alcuni chiarimenti non sarebbero stati dannosi per chiunque altro leggesse questo in futuro. E la domanda bonus dovrebbe essere probabilmente una domanda completamente separata, in quanto non è realmente correlata all'originale. – dvide

+1

Su SO non c'è niente di sbagliato nel postare nuove risposte migliori a domande a cui è già stata data una risposta. E se ti senti in colpa, accetta la risposta di DeadMG, ma inserisci il tuo sotto. :) – jalf

risposta

4

Hai dimenticato di std :: inoltrare l'argomento T & & al costruttore auto_cast_wrapper. Questo rompe la catena di inoltro. Il compilatore ora dà un avvertimento ma sembra funzionare bene.

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(); 
} 

di stampare una coppia di cinque.

+0

Quindi intendi che il costruttore dovrebbe essere "auto_cast_wrapper (T && pX): mX (std :: forward (pX))'? Penso che tu abbia ragione, in realtà, ha senso. Ma ora penso che esibisca un comportamento indefinito. Penso che il letterale vada fuori campo prima che l'operatore di conversione possa essere licenziato. Quindi sembra che questo non possa funzionare comunque da rvalues? – dvide

+0

@dvide: l'ho testato con alcuni rvalues ​​- ad esempio, restituito dal valore di una funzione - e sembra funzionare. – Puppy

+0

@DeadMG: qual è il tuo avviso? E su quale compilatore? Ricevo l'avviso: il membro di riferimento è inizializzato su un valore temporaneo che non si mantiene dopo la chiusura del costruttore. E quando lo provo il valore di 'c' è incasinato. Potrebbe essere che sembra funzionare, dato che è tecnicamente indefinito? – dvide

2

Siamo spiacenti per la pubblicazione di codice non testato. :)

DeadMG è corretto che l'argomento debba essere inoltrato. Credo che l'avviso sia falso e che MSVC abbia un bug. Considerate dalla chiamata:

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

T() vivranno alla fine della piena espressione, che significa che la funzione auto_cast, costruzione s' il auto_cast_wrapper, e la conversione definita dall'utente sono tutti riferimento a un oggetto ancora valido.

(Dal momento che l'involucro non può fare altro che convertire o distruggersi, non può sopravvivere il valore che è stato passato in auto_cast.)

posso risolvere potrebbe essere quello di rendere il membro solo un T. Tuttavia, effettuerai una copia/sposta invece di lanciare direttamente l'oggetto originale. Ma forse con l'ottimizzazione del compilatore va via.


E no, l'inoltro non è superfluo. Mantiene la categoria del valore di ciò che stiamo convertendo automaticamente:

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

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

E se non sbaglio l'operatore di conversione non deve essere (di certo non ha bisogno di essere) ha segnato const.

+0

Grazie GMan. Non sapevo che avrebbe dovuto durare fino alla fine dell'intera espressione. Ciò ha senso. Immagino che questo sia solo un bug allora. E grazie per il perfetto chiarimento di inoltro. E 'carino – dvide

+0

@dvide: nessun problema. Non impegnare al 100% che si tratta di un bug, ma ne sono abbastanza sicuro. È ancora solo un riferimento (come la versione 'const &'), solo in grado di essere un lvalue o valore, quindi penso che dovrebbe essere lo stesso. Grazie per il check-out. – GManNickG

+0

Sì, ero un po 'confuso con l'incongruenza della vita temporanea tra le due versioni. Per quanto ho potuto dire dovrebbero essere uguali, ma mi sto ancora abituando a rvalore di riferimenti quindi sono molto cauto nel saltare a qualsiasi conclusione =) – dvide

2

Il motivo per cui non viene compilato è la stessa ragione per cui questo non può essere compilato:

float rvalue() { return 5.0f } 

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

Come a per sé è un lvalue non può essere associato a b. Nel costruttore auto_cast_wrapper avremmo dovuto usare lo std::forward<T> sull'argomento per risolvere il problema. Si noti che possiamo semplicemente usare std::move(a) nell'esempio specifico sopra, ma questo non riguarderebbe il codice generico che dovrebbe funzionare anche con lvalues. Così il costruttore auto_cast_wrapper ora diventa:

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

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

    T&& mX; 
}; 

Purtroppo sembra che adesso presenta un comportamento indefinito.Ottengo il seguente avviso:

avviso C4413: 'auto_cast_wrapper :: mX': membro di riferimento è inizializzato a una temporanea che non persistono dopo il costruttore esce

Sembra che il letterale va fuori ambito prima che l'operatore di conversione possa essere licenziato. Anche se questo potrebbe essere solo un bug del compilatore con MSVC 10.0. Da GMan's answer, la durata di un temporaneo dovrebbe rimanere attiva fino alla fine dell'espressione completa.

Problemi correlati