2010-08-07 16 views
11

tuple in boost e TR1/C++ 0x fornisce un metodo conveniente (per il writer della funzione) per restituire due valori da una funzione - tuttavia sembra danni una delle principali caratteristiche del linguaggio per il chiamante: la capacità di utilizzare semplicemente la funzione per inizializzare una variabile:Come inizializzare con più valori di ritorno in C++ (0x)

T happy(); 
const auto meaningful_name(happy()); // RVO means no excess copies 

ma per:

tuple<T,U> sad(); 

ci sia hanno a cedere la capacità di scegliere un nome significativo per i nostri valori di ritorno e utilizzare get<n>() in tutto il mondo:

const auto two_unrelated_things(sad()); 

o effettuare una temporanea:

const auto unwanted_named_temporary(sad()); 
const auto one_name(get<0>(unwanted_named_temporary)); 
const auto two_name(get<1>(unwanted_named_temporary)); 

o passare da inizializzazione assegnazione, che funziona solo se i tipi sono assegnabili, e rompe auto:

tuple_element<0, decltype(sad())>::type one_mutable; // there might be a less 
tuple_element<1, decltype(sad())>::type two_mutable; // verbose way 
tie(one_mutable,two_mutable) = sad(); 

o fare qualcosa di innaturale per una classe locale:

const struct ugh { 
    ugh(decltype(sad()) rhs) : one_name(get<0>(rhs)), two_name(get<1>(rhs)) {} 
    const tuple_element<0, decltype(sad())>::type one_name; 
    const tuple_element<1, decltype(sad())>::type two_name; 
} stuff(sad()); // at least we avoid the temporary and get initialization 

C'è un modo migliore? Sto usando i costrutti compatibili VC10 sopra, qualche cosa in pieno C++ 0x o aiuto di spinta?

ideale sarebbe:

  • mi permette di utilizzare l'inizializzazione, non solo l'assegnazione
  • lasciare che il chiamante scegliere i nomi per i
  • non fare più copie restituite-in variabili
  • funziona sia per le variabili stack che per i membri della classe
  • potrebbe essere una grande libreria di modelli pazza, ma avere una sintassi sana per il chiamante e il writer delle funzioni
+2

Domanda interessante, anche se non vedo come si possano definire variabili di tipi diversi in una singola espressione. - Penso che l'opzione "o fare un temporaneo" potrebbe essere OK, se cambi le variabili nominate in riferimenti (evitando di copiare). – UncleBens

+0

Un buon punto sui riferimenti: penso che sia una soluzione per le variabili dello stack. Ho provato a fare lo stesso in una classe: class C { pubblico: C sr () (triste()), uno (ottenere <0> (SR)), due (ottenere <1> (SR)) {} const T & one; const U & two; privato: tupla sr; Ma sembra in VC10, C è due puntatori più grandi della tupla, non un grosso problema ma una specie di zoppa - non sarebbe legale per il compilatore riconoscere che i riferimenti sono alias e non allocare spazio nel caso per loro? Non è questo il motivo per cui i riferimenti ai riferimenti sono illegali in primo luogo? – BCoates

+0

Con una classe, se i dati sono memorizzati come una tupla, è possibile fornire solo metodi di accesso nominati, che chiamano il rispettivo 'get '. Dubito che ci sarà una soluzione basata su "modello pazzo", perché il linguaggio di base non sembra supportare quello che stai chiedendo. Forse potresti semplicemente ridurre il numero di caratteri che devi digitare con i macro ... – UncleBens

risposta

2
std::tuple<Type1, Type2> returnValue = sad(); 
Type1& first = std::get<0>(returnValue); 
Type2& second = std::get<1>(returnValue); 

io non sono sicuro di quello che i vostri quarto mezzo di proiettile, ma che soddisfa tutto il resto.

* modifica: in base al tuo commento sopra, ho capito cosa intendevi per quarto punto.

struct Object { 
    Object(const std::tuple<Type1, Type2>& t) : value(t) { } 
    Type1& First() { return std::get<0>(value); } 
    Type2& second() { return std::get<1>(value); } 
private: 
    std::tuple<Type1, Type2> value; 
} 

Modificare secondo necessità.

Si potrebbe anche solo non si utilizza std::tuple affatto se i valori restituiti sono così estranei che bisogna dividerli in modo per loro di essere utilizzate ragionevolmente. Le persone che hanno ottenuto per anni restituito struct s con i campi ragionevolmente denominati o accettando i parametri di riferimento per l'output.

Per inciso, ti sembra di essere innamorato di auto. Non essere. È una grande funzionalità, ma questo è non il modo in cui dovrebbe essere utilizzato. Il tuo codice diventerà illeggibile se non specifichi tipi di volta in volta.

+0

Come penso al bit "membro della classe", è questa anche una situazione in cui è possibile escludere tutte le copie? Penso che finirai per copiare "value" almeno una volta in qualsiasi circostanza, il che ovviamente richiederà la copia dei suoi membri almeno una volta. Quindi probabilmente potresti anche solo memorizzare i valori direttamente e dimenticare di memorizzare la tupla. –

+0

Sono d'accordo con non usare 'std :: tuple' quando non è appropriato - sono solo preoccupato che diventerà idiomatico e renderò il codice meno comprensibile e pieno di' get (a volte) ' Detto questo , sembra che mi preoccupi troppo delle copie extra: 'tie (a, b) = sad()' in un costruttore elide tutte le copie dei membri della tupla quando triste() restituisce make_tuple (...) ' . Forse la lezione è "i membri dei dati const sono una seccatura". – BCoates

+1

@bcoates: per quanto riguarda l'uso errato e errato della tupla. La libreria standard è un po 'colpevole quando rappresenta intervalli come 'pair '. OTOH, boost utilizza un'alternativa superiore: 'iterator_range ' con un metodo 'begin()' e 'end()'. Un range non è una coppia astratta, 'first' e' second' hanno un significato concreto! - Non penso che si dovrebbe andare molto tuple-happy in codice non generico. – UncleBens