Std :: tie nasıl çalışır?


120

std::tieÇok düşünmeden kullandım . İşe yarıyor, bu yüzden şunu kabul ettim:

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
}

Ama bu kara büyü nasıl çalışıyor? Geçici bir std::tiedeğişim tarafından nasıl yaratılır ave b? Bunu daha ilginç buluyorum çünkü bu bir kütüphane özelliği, bir dil özelliği değil, bu yüzden kesinlikle kendimizi uygulayıp anlayabileceğimiz bir şey.

Yanıtlar:


152

Temel kavramı netleştirmek için daha basit bir örneğe indirelim. Daha std::tiefazla değer döndüren işlevler (bir demet) için yararlı olsa da , bunu tek bir değerle gayet iyi anlayabiliriz:

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

İlerlemek için bilmemiz gereken şeyler:

  • std::tie bir referans demeti oluşturur ve döndürür.
  • std::tuple<int>ve std::tuple<int&>aralarında hiçbir bağlantı olmayan, aynı şablondan oluşturulmuş 2 tamamen farklı sınıftır std::tuple.
  • tuple, cppreference'denoperator= her üyenin ayrı ayrı atandığı farklı türlerde (ancak aynı sayıda) kabul eden bir demete sahiptir :

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

    (3) Tüm i için, atar std::get<i>(other)için std::get<i>(*this).

Sonraki adım, yalnızca önünüze çıkan işlevlerden kurtulmaktır, böylece kodumuzu buna dönüştürebiliriz:

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

Bir sonraki adım, bu yapıların içinde tam olarak ne olduğunu görmektir. Bunun için, operasyonlarımız için çıplak asgari düzeye indirilmiş 2 tip Tikame std::tuple<int>ve Trikame oluşturuyorum std::tuple<int&>:

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
}

Son olarak, yapılardan hep birlikte kurtulmayı seviyorum (peki,% 100 eşdeğer değil, ama bizim için yeterince yakın ve buna izin verecek kadar açık):

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
}

Yani temel olarak, std::tie(a)bir veri üyesi referansını başlatır a. std::tuple<int>(24)değeri olan bir veri üyesi yaratır 24ve atama, birinci yapıdaki veri üyesi referansına 24 atar. Ancak bu veri üyesi, atemelde atanan bir referans olduğu 24için a.


1
Beni rahatsız eden şey, atama operatörünü bir rvalue için çağırmamız.
Adam Zahran

Gelen bu cevap, bir kap başvuru tutamayacak belirtti. Neden tuplebir referans olabilir?
nn0p

6
@ nn0p std::tuplebir kapsayıcı değildir, en azından C ++ terminolojisinde değildir, std::vectorve benzerleriyle aynı değildir . Örneğin, farklı türde nesneler içerdiği için bir demet üzerinde alışılmış yollarla yineleme yapamazsınız.
bolov

@Adam kravat (x, y) = make_pair (1,2); aslında std :: tie (x, y) .operator = (std :: make_pair (1, 2)) olur, bu yüzden "bir rvalue atama" XD
Ju Piece

30

Bu, sorunuza hiçbir şekilde cevap vermiyor, ancak yine de göndermeme izin verin çünkü C ++ 17 temelde hazırdır (derleyici desteği ile), bu nedenle modası geçmiş şeylerin nasıl çalıştığını merak ederken, muhtemelen şu anki duruma bakmaya değer ve gelecek, C ++ sürümü de çalışır.

C ++ 17 ile std::tie, yapılandırılmış bağlar denen şeyin lehine hemen hemen çizebilirsiniz . Aynı şeyi yaparlar (yani, aynı değil , ancak aynı net etkiye sahiptirler), daha az karakter yazmanız gerekmesine rağmen, kütüphane desteğine ihtiyaç duymaz ve eğer böyle olursa, referans alma yeteneğiniz de vardır. Ne istiyorsunuz.

(C ++ 17'de kurucuların bağımsız değişken çıkarımı yaptığına dikkat edin, bu yüzden make_tuplede biraz gereksiz hale geldi.)

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
Bu son satır derlenirse biraz endişeliyim. Yasadışı olan geçici bir referansı bağlamak gibi görünüyor.
Nir Friedman

3
@Neil Bir rvalue referansı veya sabit bir değer referansı olması gerekir. Bir ldeğer başvurusunu bir prvalue'ya (geçici) bağlayamazsınız. Bu MSVC'de çağlar boyunca bir "uzantı" olmasına rağmen.
Nir Friedman

1
Muhtemelen tie, yapılandırılmış bağlamaların aksine , varsayılan olarak oluşturulamayan türlerde bu şekilde kullanılabileceğinden bahsetmeye değer .
Dan

5
Evet, std::tie()yapılandırılmış bağlamaların genellikle üstün olduğu C ++ 17'den bu yana çok daha az kullanışlıdır, ancak mevcut (aynı anda yeni bildirilmemiş) değişkenlere atama ve kısa bir şekilde birden çok değişkeni veya diğer şeyleri değiştirme gibi diğer şeyleri de içeren kullanımları vardır. referanslara atanmalıdır.
underscore_d
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.