Jak działa std :: tie?

120

Użyłem std::tiebez zastanawiania się nad tym. Działa, więc właśnie zaakceptowałem, że:

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
}

Ale jak działa ta czarna magia ? W jaki sposób tymczasowe tworzone przez std::tiezmianę ai b? Uważam to za bardziej interesujące, ponieważ jest to funkcja biblioteki, a nie funkcja języka, więc z pewnością jest to coś, co możemy zaimplementować i zrozumieć.

bolov
źródło

Odpowiedzi:

152

Aby wyjaśnić podstawową koncepcję, sprowadzimy ją do bardziej podstawowego przykładu. Chociaż std::tiejest to przydatne w przypadku funkcji zwracających (krotkę) więcej wartości, możemy to zrozumieć, mając tylko jedną wartość:

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

Rzeczy, które musimy wiedzieć, aby iść naprzód:

  • std::tie konstruuje i zwraca krotkę odwołań.
  • std::tuple<int>i std::tuple<int&>są 2 zupełnie różne klasy, bez połączenia między nimi, inni, że zostały one wygenerowane z tego samego szablonu std::tuple.
  • krotka ma operator=akceptację krotki różnych typów (ale tej samej liczby), w której każdy element członkowski jest przypisywany indywidualnie - z cppreference :

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

    (3) Dla wszystkich i, przypisuje std::get<i>(other)do std::get<i>(*this).

Następnym krokiem jest pozbycie się tych funkcji, które tylko przeszkadzają, abyśmy mogli przekształcić nasz kod do tego:

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

Następnym krokiem jest sprawdzenie, co dokładnie dzieje się wewnątrz tych struktur. W tym celu tworzę 2 typy Tpodstawników std::tuple<int>i Trpodstawników std::tuple<int&>, zredukowane do minimum dla naszych operacji:

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
}

I wreszcie, lubię pozbywać się wszystkich struktur razem (cóż, nie jest to w 100% równoważne, ale jest wystarczająco blisko dla nas i wystarczająco wyraźne, aby na to pozwolić):

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
}

Zasadniczo std::tie(a)inicjalizuje odwołanie do elementu członkowskiego danych a. std::tuple<int>(24)tworzy element danych z wartością 24, a przypisanie przypisuje 24 do odniesienia do elementu danych w pierwszej strukturze. Ale ponieważ ten element członkowski danych jest powiązanym odniesieniem a, w zasadzie przypisuje się 24do a.

bolov
źródło
1
Martwi mnie to, że wywołujemy operator przypisania do wartości r.
Adam Zahran
W tej odpowiedzi stwierdzono, że kontener nie może pomieścić odniesienia. Dlaczego tuplemiałbym mieć odniesienie?
nn0p
6
@ nn0p std::tuplenie jest kontenerem, przynajmniej nie w terminologii C ++, nie jest tym samym, co std::vectorlubi i. Na przykład nie możesz iterować zwykłymi sposobami po krotce, ponieważ zawiera ona różne typy obiektów.
bolov
@Adam tie (x, y) = make_pair (1,2); faktycznie staje się std :: tie (x, y) .operator = (std :: make_pair (1, 2)), dlatego „przypisanie do wartości r” działa XD
Ju Piece
30

To w żaden sposób nie odpowiada na twoje pytanie, ale mimo wszystko pozwól, że napiszę, ponieważ C ++ 17 jest w zasadzie gotowy (z obsługą kompilatora), więc zastanawiając się, jak działają przestarzałe rzeczy, prawdopodobnie warto przyjrzeć się, jak bieżący, i przyszła wersja C ++ też działa.

W C ++ 17 możesz w dużym stopniu odciąć się std::tieod tego, co nazywa się powiązaniami strukturalnymi . Robią to samo (no cóż, nie to samo , ale mają ten sam efekt netto), chociaż musisz wpisać mniej znaków, nie wymaga to obsługi biblioteki, a także masz możliwość pobierania referencji, jeśli tak się stanie czego chcesz.

(Zwróć uwagę, że w C ++ 17 konstruktory wykonują dedukcję argumentów, więc make_tuplerównież stały się zbyteczne).

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
Damon
źródło
2
Jeśli ta ostatnia linia się skompiluje, jestem trochę zaniepokojony. Wygląda na to, że wiąże odwołanie do tymczasowego, co jest nielegalne.
Nir Friedman
3
@Neil Musi to być odwołanie do wartości r lub referencja do wartości stałej l. Nie można powiązać odwołania lwartość z prvalue (tymczasowo). Chociaż od wieków jest to „rozszerzenie” w MSVC.
Nir Friedman
1
Prawdopodobnie warto również wspomnieć, że w przeciwieństwie do tiepowiązań strukturalnych można używać w ten sposób na typach, których nie można konstruować domyślnie.
Dan
5
Tak, std::tie()jest znacznie mniej przydatny od czasu C ++ 17, gdzie powiązania strukturalne są zwykle lepsze, ale nadal ma zastosowania, w tym przypisywanie do istniejących (nie jednocześnie nowo zadeklarowanych) zmiennych i zwięzłe wykonywanie innych rzeczy, takich jak zamiana wielu zmiennych lub inne rzeczy, które należy przypisać do odniesień.
underscore_d