Come funziona std :: tie?


120

L'ho usato std::tiesenza pensarci troppo. Funziona quindi ho appena accettato che:

auto test()
{
   int a, b;
   std::tie(a, b) = std::make_tuple(2, 3);
   // a is now 2, b is now 3
   return a + b; // 5
}

Ma come funziona questa magia nera ? Come funziona un temporaneo creato dal std::tiecambiamento ae b? Lo trovo più interessante poiché è una funzionalità di libreria, non una funzionalità di linguaggio, quindi sicuramente è qualcosa che possiamo implementare e capire da soli.

Risposte:


152

Per chiarire il concetto di base, riduciamolo a un esempio più semplice. Sebbene std::tiesia utile per le funzioni che restituiscono (una tupla di) più valori, possiamo capirlo bene con un solo valore:

int a;
std::tie(a) = std::make_tuple(24);
return a; // 24

Cose che dobbiamo sapere per andare avanti:

  • std::tie costruisce e restituisce una tupla di riferimenti.
  • std::tuple<int>e std::tuple<int&>sono 2 classi completamente differenti, senza connessione tra loro, altre che sono state generate dallo stesso modello, std::tuple.
  • tuple ha operator=un'accettazione di una tupla di diversi tipi (ma lo stesso numero), in cui ogni membro viene assegnato individualmente, da cppreference :

    template< class... UTypes >
    tuple& operator=( const tuple<UTypes...>& other );

    (3) Per tutti i, assegna std::get<i>(other)a std::get<i>(*this).

Il prossimo passo è sbarazzarsi di quelle funzioni che ti ostacolano, così possiamo trasformare il nostro codice in questo:

int a;
std::tuple<int&>{a} = std::tuple<int>{24};
return a; // 24

Il passo successivo è vedere esattamente cosa succede all'interno di quelle strutture. Per questo, creo 2 tipi di Tsostituenti std::tuple<int>e Trsostituenti std::tuple<int&>, ridotti al minimo per le nostre operazioni:

struct T { // substituent for std::tuple<int>
    int x;
};

struct Tr { // substituent for std::tuple<int&>
    int& xr;

    auto operator=(const T& other)
    {
       // std::get<I>(*this) = std::get<I>(other);
       xr = other.x;
    }
};

auto foo()
{
    int a;
    Tr{a} = T{24};

    return a; // 24
}

E infine, mi piace sbarazzarmi delle strutture tutte insieme (beh, non è equivalente al 100%, ma è abbastanza vicino per noi e abbastanza esplicito da consentirlo):

auto foo()
{
    int a;

    { // block substituent for temporary variables

    // Tr{a}
    int& tr_xr = a;

    // T{24}
    int t_x = 24;

    // = (asignement)
    tr_xr = t_x;
    }

    return a; // 24
}

Quindi, in pratica, std::tie(a)inizializza un riferimento al membro dati a a. std::tuple<int>(24)crea un membro dati con valore 24e l'assegnazione assegna 24 al riferimento membro dati nella prima struttura. Ma poiché quel membro dati è un riferimento associato a a, che in pratica assegna 24a a.


1
Quello che mi infastidisce è che stiamo chiamando l'operatore di assegnazione a un rvalue.
Adam Zahran

In questa risposta, si afferma che un contenitore non può contenere un riferimento. Perché tuplepotrebbe contenere un riferimento?
nn0p

6
@ nn0p std::tuplenon è un contenitore, almeno non nella terminologia C ++, non è uguale a std::vectore simili. Ad esempio, non puoi iterare con i soliti modi su una tupla perché contiene diversi tipi di oggetti.
bolov

@Adam tie (x, y) = make_pair (1,2); in realtà diventa std :: tie (x, y) .operator = (std :: make_pair (1, 2)), è per questo che "l'assegnazione a un rvalue" funziona XD
Ju Piece il

30

Questo non risponde in alcun modo alla tua domanda, ma lascia che lo pubblichi comunque perché C ++ 17 è praticamente pronto (con il supporto del compilatore), quindi mentre ti chiedi come funziona la roba obsoleta, probabilmente vale la pena guardare come l'attuale, e futuro, funziona anche la versione di C ++.

Con C ++ 17 puoi praticamente grattare std::tiea favore di quelli che vengono chiamati collegamenti strutturati . Fanno lo stesso (beh, non lo stesso , ma hanno lo stesso effetto netto), anche se è necessario digitare meno caratteri, non ha bisogno del supporto della libreria e hai anche la possibilità di prendere riferimenti, se questo è ciò che vuoi.

(Si noti che in C ++ 17 i costruttori eseguono la deduzione degli argomenti, quindi make_tupleè diventato anche un po 'superfluo.)

int a, b;
std::tie(a, b) = std::make_tuple(2, 3);

// C++17
auto  [c, d] = std::make_tuple(4, 5);
auto  [e, f] = std::tuple(6, 7);
std::tuple t(8,9); auto& [g, h] = t; // not possible with std::tie

2
Se l'ultima riga viene compilata, sono un po 'preoccupato. Sembra che leghi un riferimento a un temporaneo che è illegale.
Nir Friedman

3
@Neil Deve essere un riferimento rvalue o un riferimento const lvalue. Non è possibile associare un riferimento lvalue a un prvalue (temporaneo). Sebbene questa sia stata una "estensione" in MSVC per secoli.
Nir Friedman

1
Probabilmente vale anche la pena ricordare che tie, a differenza dei binding strutturati, possono essere usati in questo modo su tipi che non sono costruibili per impostazione predefinita.
Dan

5
Sì, std::tie()è molto meno utile da C ++ 17, dove i collegamenti strutturati sono solitamente superiori, ma ha ancora usi, inclusa l'assegnazione a variabili esistenti (non dichiarate contemporaneamente di recente) e altre cose concise come scambiare più variabili o altre cose che deve assegnare ai riferimenti.
underscore_d
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.