2010-05-01 12 views
13

(ho chiesto una variante di questa domanda comp.std.C++ ma non ha ottenuto una risposta.)C++ 0x rvalue riferimenti e provvisori

Perché la chiamata a f(arg) in questo codice di chiamata del const sovraccarico ref di f?

void f(const std::string &); //less efficient 
void f(std::string &&); //more efficient 

void g(const char * arg) 
{ 
    f(arg); 
} 

Il mio intuito dice che il sovraccarico di f(string &&) deve essere scelto, perché arg deve essere convertito in una temporanea non importa cosa, e la temporanea corrisponde al riferimento rvalue meglio il riferimento lvalue.

Questo non è ciò che accade in GCC e MSVC (modifica: Grazie Sumant: non si verifica in GCC 4.3-4.5). In almeno G ++ e MSVC, qualsiasi lvalue non si associa a un argomento di riferimento del valore di riferimento, , anche se è stato creato temporaneamente un intermedio. Infatti, se il sovraccarico const ref non è presente, i compilatori diagnosticano un errore. Tuttavia, la scrittura f(arg + 0) o f(std::string(arg))fa sceglie il sovraccarico di riferimento di rvalue come ci si aspetterebbe.

Dalla mia lettura dello standard C++ 0x, sembra che la conversione implicita di un const-char * in una stringa debba essere considerata quando si considera se f(string &&) è valido, proprio come quando si passa un argomento di riferimento di valore const. La sezione 13.3 (risoluzione di sovraccarico) non distingue tra refs di riferimento e riferimenti const in troppe posizioni. Inoltre, sembra che la regola che impedisce ai lvalue di legarsi ai riferimenti di valore (13.3.3.1.4/3) non dovrebbe applicarsi se c'è un temporaneo intermedio - dopo tutto, è perfettamente sicuro spostarsi dal temporaneo.

è questa:

  1. Me travisamento/fraintendere lo standard, in cui il comportamento attuato è il comportamento previsto, e c'è qualche buona ragione per cui il mio esempio dovrebbe comportarsi il modo in cui lo fa?
  2. Un errore che i produttori di compilatori hanno in qualche modo realizzato tutti? O un errore basato su strategie di implementazione comuni? O un errore, ad es. GCC (dove è stata implementata questa regola di riferimento dei valori di lvalue/rvalue), che è stata copiata da altri fornitori?
  3. Un difetto nello standard o una conseguenza non intenzionale o qualcosa che dovrebbe essere chiarito?

EDIT: Ho una domanda di follow-on che è legato: C++0x rvalue references - lvalues-rvalue binding

+0

Nella situazione data, perché il sovraccarico potrebbe essere più veloce? Un oggetto stringa deve essere creato in un modo o nell'altro e un riferimento associato ad esso. – UncleBens

+0

@UncleBens: Supponiamo che la funzione stia memorizzando il suo argomento in qualche variabile globale/variabile membro - se l'argomento è noto per essere mobile, quindi può essere spostato nella destinazione, piuttosto che copiato. – Doug

risposta

8

GCC sta facendo male secondo l'FCD. La FCD dice alla 8.5.3 circa riferimento vincolante

  • Se il riferimento è un riferimento lvalue e l'espressione di inizializzazione è un [lvalue/tipo di classe] ...
  • In caso contrario, il riferimento sarà un riferimento a un valore assegnabile tipo const non volatile (cioè, cv1 deve essere const), o il riferimento deve essere un riferimento di valore e l'espressione dell'inizializzatore deve essere un valore rval o avere un tipo di funzione.

Il vostro caso per la chiamata al std::string && corrisponde nessuno di loro, perché l'inizializzatore è un lvalue. Non arriva al luogo per creare un valore provvisorio, perché quel proiettile di primo livello richiede già un rvalore.

Ora, la risoluzione di sovraccarico non utilizza direttamente il binding di riferimento per verificare se esiste una sequenza di conversione implicita. Invece, si dice a 13.3.3.1.4/2

Quando un parametro di tipo riferimento non è associato direttamente a un'espressione discussione, la sequenza di conversione è quella richiesta per convertire l'espressione argomento al tipo sottostante di riferimento secondo 13.3. 3.1.

Così, la risoluzione di sovraccarico determina un vincitore, anche se quel vincitore potrebbe non essere in grado di legarsi a tale argomento. Per esempio:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); 
void f(B); 
int main() { A a; f(a.bits); } 

riferimento vincolante a 8.5 vieta campi di bit di legarsi a riferimenti lvalue. Ma la risoluzione di sovraccarico indica che la sequenza di conversione è quella che converte in int, riuscendo così anche se quando la chiamata viene effettuata in seguito, la chiamata è mal formata. Quindi il mio esempio di bitfield è mal formato. Se dovesse scegliere la versione B, sarebbe riuscita, ma era necessaria una conversione definita dall'utente.

Tuttavia, esistono due eccezioni per tale regola. Questi sono

Eccetto per un parametro oggetto implicito, per cui vedi 13.3.1, una sequenza conversione standard non possono essere formate se richiede un riferimento vincolante per lvalue non const a un rvalue o associare un riferimento ad un rvalue lvalue.

Così, la seguente chiamata è valida:

struct B { B(int) { /* ... */ } }; 
struct A { int bits: 1; }; 

void f(int&); /* binding an lvalue ref to non-const to rvalue! */ 
void f(B); 
int main() { A a; f(1); } 

E così, il vostro esempio chiama la versione const T&

void f(const std::string &); 
void f(std::string &&); // would bind to lvalue! 

void g(const char * arg) { f(arg); } 

Tuttavia, se dici f(arg + 0), si crea un rvalue, e quindi la seconda funzione è valida.

+0

Ah, non ho letto 8.5.3 troppo da vicino, grazie. Sembra che le regole per i riferimenti di binding siano inutilmente rigide - Non vedo che ci sia un caso d'uso convincente per rifiutare tale codice, ma può (inaspettatamente, IMO) causare copie non intenzionali - ad es. se hai chiamato il vettore :: push_back con un argomento const char * lvalue. – Doug

+1

Un'ultima cosa: la proibizione per i parametri non costanti che accettano i temporaries (per me) è specifica in modo da non perdere accidentalmente alcuna informazione "extra" che una funzione potrebbe restituire attraverso quel riferimento. Ma questa considerazione non si applica ad un valore di riferimento, che sia const o non-const - è concettualmente "non un valore utile" dopo la chiamata. Infine, il fatto che f (arg) non è consentito ma f (arg + 0) è permesso, o che ad es. stringvec.push_back (string (charptr)) è * più efficiente * di stringvec.push_back (charptr) non mi sembra molto intuitivo. – Doug

+0

Questa risposta non è aggiornata, vero? La bozza corrente richiede il prelievo del secondo sovraccarico senza errori. – sellibitze

1

Un sacco di cose nella bozza attuale dello standard hanno bisogno di chiarimenti, se mi chiedete. E i compilatori sono ancora in fase di sviluppo, quindi è difficile fidarsi del loro aiuto.

Sembra abbastanza chiaro che la tua intuizione sia giusta ... i temporanei di qualsiasi tipo dovrebbero legarsi a riferimenti di valore. Ad esempio, §3.10, la nuova sezione "tassonomia", definisce categoricamente i provvisori come rvalue.

Il problema può essere che la specifica dell'argomento RR non è sufficiente per richiamare la creazione di un temporaneo. §5.2.2/5: "Quando un parametro è di tipo const riferimento viene introdotto un oggetto temporaneo, se necessario." Sembra sospettosamente esclusivo.

Sembra di scivolare di nuovo tra le crepe al §13.3.3.1/6: (enfasi mia)

Quando il tipo di parametro non è un riferimento, impliciti modelli di sequenza conversione copia-inizializzazione del parametro dall'espressione argomento. La sequenza di conversione implicita è quella richiesta per convertire l'espressione dell'argomento in un valore del tipo del parametro.

Si noti che l'inizializzazione della copia string &&rr = "hello"; funziona correttamente in GCC.

MODIFICA: In realtà il problema non esiste sulla mia versione di GCC. Sto ancora cercando di capire come la seconda conversione standard della sequenza di conversione definita dall'utente si riferisca alla formazione di un riferimento di rvalue. (È la formazione RR una conversione a tutti O è dettata da curiosità sparsi come 5.2.2/5??)

+0

Grazie - Ho perso quelle clausole che hai citato. Sembrano confondere le cose. Inoltre, il motivo per cui non ho passato una stringa letterale è perché MSVC sembra trattarli come lvalue, ma GCC no - è piuttosto strano. – Doug

2

Non ho visto il comportamento menzionato da Doug su g ++. g ++ 4.5 e 4.4.3 entrambi chiamano f(string &&) come previsto, ma VS2010 chiama f(const string &). Quale versione g ++ stai usando?

+0

+1, vorrei aver verificato il problema prima di saltare dentro! – Potatoswatter

+0

Ho ricontrollato in G ++ e hai ragione - non succede. Sono sicuro di averlo controllato lì prima! Sembra che forse il problema è solo in MSVC, o non è chiaro nello standard. – Doug

0

Non so se questo è cambiato nelle ultime versioni dello standard, ma era solito dire qualcosa come "in caso di dubbio, non utilizzare il riferimento di rvalue". Probabilmente per ragioni di compatibilità.

Se si desidera spostare la semantica, utilizzare f(std::move(arg)), che funziona con entrambi i compilatori.

6

Era un difetto nella bozza standard che hai letto. Questo difetto è entrato in un effetto collaterale di alcune modifiche ansiose per impedire il legame dei riferimenti di valore ai valori per motivi di sicurezza.

Il tuo intuito è giusto. Ovviamente, non vi è alcun danno nel consentire ad un riferimento di rvalue di riferirsi ad alcuni temporali senza nome anche se l'inizializzatore era un'espressione lvalue. Dopo tutto, questo è ciò che i riferimenti di valore valgono per. Il problema che hai osservato è stato risolto l'anno scorso. Lo standard imminente imporrà che il secondo sovraccarico verrà selezionato nell'esempio in cui il riferimento del valore di riferimento si riferirà ad un oggetto stringa temporaneo.

La regola correzione ne fece il progetto di n3225.pdf (2010-11-27):

  • [...]
  • In caso contrario, il riferimento sarà un riferimento a un valore assegnabile tipo const non volatile (cioè, cv1 deve essere const), o il riferimento deve essere un riferimento di valore e l'espressione dell'inizializzatore deve essere un valore rval o avere un tipo di funzione . [...]
    • [...]
    • In caso contrario, una temporanea di [...] è stato creato [...]
 double&& rrd3 = i; // rrd3 refers to temporary with value 2.0 

Ma N3225 sembra essersi perso per dire cosa sia in questo esempio i.L'ultima bozza N3290 contiene questi esempi:

 double d2 = 1.0; 
     double&& rrd2 = d2; // error: copying lvalue of related type 
     int i3 = 2; 
     double&& rrd3 = i3; // rrd3 refers to temporary with value 2.0 

Dal momento che la versione di MSVC è stato rilasciato prima di questo problema è stato riparato, gestisce ancora i riferimenti rvalue secondo le vecchie regole. Si prevede che la prossima versione di MSVC implementerà le nuove regole di riferimento del valore di riferimento (soprannominato "rvalue references 2.1" dagli sviluppatori MSVC) see link.