std :: tieはどのように機能しますか?


120

std::tieあまり考えずに使ってきました。それは機能するので、私はちょうどそれを受け入れました:

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
}

しかし、この黒魔術はどのように機能するのでしょうか?どのように一時的により作成さんstd::tieの変化ab?これは言語機能ではなくライブラリ機能であるため、私はこれをもっと興味深いものにしています。

回答:


152

コアコンセプトを明確にするために、より基本的な例に減らしましょう。がstd::tie複数の値を(タプル)を返す関数のために有用である、私たちはただ一つの値でうまくそれを理解することができます:

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

先に進むために知っておくべきこと:

  • std::tie 参照のタプルを作成して返します。
  • std::tuple<int>およびstd::tuple<int&>は2つの完全に異なるクラスであり、それらの間には接続がありません。それ以外は、同じテンプレートから生成されたものstd::tupleです。
  • タプルには、operator=さまざまなタイプ(同じ数)のタプルが受け入れられ、各メンバーはcppreferenceから個別に割り当てられます

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

    (3)全てのiについて、割り当てstd::get<i>(other)std::get<i>(*this)

次のステップは、邪魔になる関数だけを取り除くことです。そのため、コードを次のように変換できます。

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

次のステップは、これらの構造の内部で何が起こるかを正確に確認することです。このTためにstd::tuple<int>、とTr置換基の2つのタイプの置換基を作成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
}

そして最後に、私はすべての構造を一緒に取り除くのが好きです(まあ、それは100%の等価ではありませんが、私たちにとっては十分に近く、それを許可するのに十分明示的です):

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
}

したがって、基本的にstd::tie(a)はへのデータメンバー参照を初期化しますastd::tuple<int>(24)value 24でデータメンバーを作成し、最初の構造体のデータメンバー参照に24を割り当てます。ただし、そのデータメンバーはにバインドされた参照であるためa、基本的にはに割り当て24られaます。


1
何がバグかと言うと、右辺値への代入演算子を呼び出しているということです。
Adam Zahran

では、この答えは、コンテナが参照を保持することができないと述べました。なぜtuple参照を保持できるのですか?
nn0p

6
@ nn0p std::tupleはコンテナではありません。少なくともC ++の用語では、などとは異なりstd::vectorます。たとえば、タプルにはさまざまなタイプのオブジェクトが含まれているため、通常の方法でタプルを反復処理することはできません。
bolov

@Adam tie(x、y)= make_pair(1,2); 実際にはstd :: tie(x、y).operator =(std :: make_pair(1、2))になります。これが、「右辺値への代入」が機能する理由ですXD
Ju Piece

30

これはどのようにもあなたの質問に答えませんが、C ++ 17は基本的に(コンパイラーのサポートにより)準備ができているので、とにかくそれを投稿させてください。将来的には、C ++のバージョンも機能します。

C ++ 17を使用するstd::tieと、いわゆる構造化バインディングを優先してスクラッチできます。それらは同じです(まあ、同じではありませんが、同じ効果があります)。入力する文字の数は少なく、ライブラリサポートは必要ありません。また、参照が発生した場合、参照を取得することもできます。あなたが欲しいもの。

(C ++ 17では、コンストラクターが引数の演繹を行うためmake_tuple、やや不必要になったことに注意してください。)

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
最後の行がコンパイルされる場合、私は少し心配です。不正な一時への参照をバインドするように見えます。
Nir Friedman

3
@Neil右辺値参照またはconst左辺値参照のいずれかである必要があります。lvalue参照をprvalue(一時)にバインドすることはできません。これは長い間、MSVCの「拡張」でしたが。
Nir Friedman

1
とは異なりtie、とは異なり、構造化バインディングは、デフォルトで構成できないタイプに対してこの方法で使用できることにも言及する価値があります。
Dan

5
ええ、std::tie()C ++ 17以降はそれほど有用ではありません。通常、構造化バインディングの方が優れていますが、既存の(同時に宣言されていない)変数への割り当てや、複数の変数の交換など、他のことを簡潔に行うなどの用途があります。参照に割り当てる必要があります。
underscore_d
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.