Bagaimana cara kerja std :: tie?

120

Saya telah menggunakan std::tietanpa terlalu memikirkannya. Ini berhasil jadi saya baru saja menerima bahwa:

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
}

Tapi bagaimana ilmu hitam ini bekerja? Bagaimana sementara dibuat oleh std::tieperubahan adan b? Menurut saya ini lebih menarik karena ini adalah fitur perpustakaan, bukan fitur bahasa, jadi tentunya ini adalah sesuatu yang dapat kita implementasikan dan pahami sendiri.

bolov
sumber

Jawaban:

152

Untuk memperjelas konsep inti, mari kita menguranginya menjadi contoh yang lebih mendasar. Meskipun std::tieberguna untuk fungsi yang mengembalikan (tuple dari) lebih banyak nilai, kita dapat memahaminya hanya dengan satu nilai:

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

Hal-hal yang perlu kita ketahui untuk maju:

  • std::tie membangun dan mengembalikan tupel referensi.
  • std::tuple<int>dan std::tuple<int&>merupakan 2 kelas yang sangat berbeda, tanpa koneksi di antara keduanya, selain itu kelas tersebut dibuat dari template yang sama std::tuple,.
  • tuple operator=menerima sebuah tuple dari jenis yang berbeda (tetapi jumlahnya sama), di mana setiap anggota ditetapkan secara individual — dari cppreference :

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

    (3) Untuk semua i, tetapkan std::get<i>(other)ke std::get<i>(*this).

Langkah selanjutnya adalah menyingkirkan fungsi-fungsi yang hanya menghalangi jalan Anda, jadi kami dapat mengubah kode kami menjadi ini:

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

Langkah selanjutnya adalah melihat dengan tepat apa yang terjadi di dalam struktur tersebut. Untuk ini, saya membuat 2 jenis Tsubstituen untuk std::tuple<int>dan Trsubstituen std::tuple<int&>, dipreteli seminimal mungkin untuk operasi kami:

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
}

Dan akhirnya, saya ingin menyingkirkan semua struktur bersama-sama (yah, ini tidak 100% setara, tetapi cukup dekat bagi kami, dan cukup eksplisit untuk memungkinkannya):

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
}

Jadi pada dasarnya, std::tie(a)menginisialisasi referensi anggota data ke a. std::tuple<int>(24)membuat anggota data dengan nilai 24, dan tugas menetapkan 24 ke referensi anggota data di struktur pertama. Tapi karena anggota data itu adalah referensi yang terikat a, yang pada dasarnya ditetapkan 24ke a.

bolov
sumber
1
Apa yang mengganggu saya adalah bahwa kita memanggil operator penugasan ke nilai r.
Adam Zahran
Dalam hal ini jawaban, itu menyatakan bahwa wadah tidak bisa menahan referensi. Kenapa tuplebisa mengadakan referensi?
nn0p
6
@ nn0p std::tuplebukan sebuah wadah, setidaknya tidak dalam terminologi C ++, tidak sama dengan std::vectordan sejenisnya. Misalnya Anda tidak dapat mengulangi dengan cara biasa melalui tupel karena berisi berbagai jenis objek.
bolov
@Adam dasi (x, y) = make_pair (1,2); sebenarnya menjadi std :: tie (x, y) .operator = (std :: make_pair (1, 2)), itu sebabnya "assignment to an rvalue" berfungsi XD
Ju Piece
30

Ini tidak menjawab pertanyaan Anda dengan cara apa pun, tetapi izinkan saya mempostingnya karena C ++ 17 pada dasarnya sudah siap (dengan dukungan kompiler), jadi sambil bertanya-tanya bagaimana hal-hal yang sudah ketinggalan zaman bekerja, mungkin ada baiknya melihat bagaimana saat ini, dan masa depan, versi C ++ juga berfungsi.

Dengan C ++ 17 Anda dapat melakukan banyak hal std::tieuntuk mendukung apa yang disebut binding terstruktur . Mereka melakukan hal yang sama (yah, tidak sama , tetapi mereka memiliki efek bersih yang sama), meskipun Anda perlu mengetik lebih sedikit karakter, itu tidak memerlukan dukungan perpustakaan, dan Anda juga memiliki kemampuan untuk mengambil referensi, jika itu kebetulan apa maumu.

(Perhatikan bahwa dalam C ++ 17 konstruktor melakukan deduksi argumen, sehingga make_tuplemenjadi agak berlebihan juga.)

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
sumber
2
Jika baris terakhir itu terkompilasi, saya agak khawatir. Sepertinya mengikat referensi ke sementara yang ilegal.
Nir Friedman
3
@Neil Ini harus berupa referensi nilai r, atau referensi nilai l konstanta. Anda tidak dapat mengikat referensi lvalue ke prvalue (sementara). Meskipun ini telah menjadi "perpanjangan" di MSVC selama berabad-abad.
Nir Friedman
1
Mungkin juga perlu disebutkan bahwa tidak seperti tie, binding terstruktur dapat digunakan dengan cara ini pada jenis yang tidak dapat dibuat secara default.
Dan
5
Ya, std::tie()jauh kurang berguna sejak C ++ 17, di mana binding terstruktur biasanya lebih unggul, tetapi masih memiliki kegunaan, termasuk menetapkan ke variabel yang ada (tidak secara bersamaan baru dideklarasikan) dan secara ringkas melakukan hal-hal lain seperti menukar beberapa variabel atau hal lain yang harus menetapkan referensi.
underscore_d