web-dev-qa-db-ja.com

C ++ 11のタプルの良いユースケースは何ですか?

C++ 11でタプルを使用するための良いユースケースは何ですか?たとえば、次のようにローカル構造体を定義する関数があります。

_template<typename T, typename CmpF, typename LessF>
void mwquicksort(T *pT, int nitem, const int M, CmpF cmp, LessF less)
{
  struct SI
  {
    int l, r, w;
    SI() {}
    SI(int _l, int _r, int _w) : l(_l), r(_r), w(_w) {}
  } stack[40];

  // etc
_

SI構造体を_std::Tuple<int,int,int>_で置き換えることを検討していました。これは、事前定義された便利なコンストラクターと演算子を含むはるかに短い宣言ですが、次の欠点があります。

  • タプル要素は、実装定義のあいまいな構造体に隠されています。 Visual Studioはその内容を適切に解釈して表示しますが、Tuple要素の値に依存する条件付きブレークポイントを配置することはできません。
  • 個々のタプルフィールド(get<0>(some_Tuple))へのアクセスは、構造体要素(_s.l_)へのアクセスよりもはるかに冗長です。
  • 名前によるフィールドへのアクセスは、数値インデックスによるよりもはるかに情報量が多く(そして短い!).

最後の2つのポイントは、tie関数によってある程度対処されます。これらの欠点を考えると、タプルの良いユースケースは何でしょうか?

[〜#〜] update [〜#〜] VS2010 SP1デバッガーは、次の配列_std::Tuple<int, int, int> stack[40]_の内容を表示できないことが判明しましたが、構造体でコーディングされている場合は正常に機能します。そのため、決定は基本的に簡単です。値を調べる必要がある場合は、構造体[esp。 GDBのようなデバッガでは重要です]。

40
zvrba

まあ、私見、最も重要な部分は汎用コードです。あらゆる種類の構造体で機能する汎用コードの作成は、タプルで機能する汎用コードの作成よりもはるかに困難です。たとえば、あなたが自分で言及した std::tie 関数は、構造体を作成することはほとんど不可能です。

これにより、次のようなことができます。

  • 遅延実行の関数パラメーターを保存します(例 この質問
  • std::tieで面倒な(アン)パッキングなしで複数のパラメーターを返します
  • (等型ではない)データセットを結合します(例えば、並列実行から)、それはstd::Tuple_catと同じくらい簡単にできます。

問題は、これらの使用法に留まらず、人々はこのリストを拡張して、構造体で行うのがはるかに難しいタプルに基づいた汎用機能を作成できることです。明日、誰かがシリアル化の目的で素晴らしい使用法を見つけるかもしれません。

26
KillianDS

関数から複数の値を返す簡単な方法です。

std::Tuple<int,int> fun();

結果値は、次のようにエレガントに使用できます。

int a;
int b;
std::tie(a,b)=fun();
57
mirk

Tuplesのほとんどの用途はstd::tie

bool MyStruct::operator<(MyStruct const &o) const
{
    return std::tie(a, b, c) < std::tie(o.a, o.b, o.c);
}

ここでの回答の他の多くの例とともに。ただし、この例は、C++ 03で使用されていた方法から多くの労力を節約できるため、最も一般的に有用であることがわかりました。

20
LB--

std::pair?使用する場所の多くはstd::Tupleも同様ですが、厳密に2つの値に制限されていません。

タプルのリストにある欠点は、std :: pairにも当てはまります。時には、firstsecondよりもメンバーの名前がより表現力豊かなタイプが必要な場合がありますが、必要ない場合もあります。同じことがタプルにも当てはまります。

13
Jonathan Wakely

一般的なライブラリ機能の実装の詳細以外では、タプルを使用するのは適切ではないと思います。

入力の(可能性のある)保存は、結果のコードの自己文書化プロパティの損失を相殺しません。

フィールドの意味のある名前を取り去るだけの構造体をタプルに置き換えて、フィールド名を「数字」に置き換えます(概念が間違っているstd :: pairの概念のように)。

タプルを使用して複数の値を返すことは、名前付きの型を返すか、名前付きの参照を使用するという選択肢よりも自己文書化がはるかに少なくなります。この自己文書化がなければ、返される値が相互に変換可能な場合、返される値の順序を混同するのは簡単です。

11
igorlord

実際のユースケースは、名前のない要素、可変長テンプレート、ラムダ関数がある状況です。両方の状況で、未知のタイプを持つ名前のない要素を持つことができるため、それらを保存する唯一の方法は名前のない要素を持つ構造体です:std :: Tuple。他のすべての状況では、既知のタイプを持つ既知の名前付け可能な要素の数があり、したがって通常の構造体を使用できます。これは、99%の優れた答えです。

たとえば、std :: Tupleを使用して、固定数の汎用入力を使用して通常の関数またはテンプレートから「複数の戻り値」を取得しないでください。そのために実際の構造を使用してください。実際のオブジェクトは、文字通り任意のインターフェースを与えることができるため、std :: Tuple cookie-cutterよりもはるかに「汎用」です。また、公共図書館での型の安全性と柔軟性が大幅に向上します。

次の2つのクラスメンバー関数を比較してください。

std::Tuple<double, double, double>  GetLocation() const; // x, y, z

GeoCoordinate  GetLocation() const;

実際の「ジオ座標」オブジェクトを使用して、親オブジェクトに位置がない場合にfalseを返す演算子bool()を提供できます。そのAPIを介して、ユーザーはx、y、zの位置を取得できます。しかし、ここに大きなものがあります。6か月後に時間フィールドを追加してGeoCoordinate 4Dを作成することに決めた場合、現在のユーザーのコードは壊れません。 std :: Tupleバージョンではできません。

6
Zack Yezek

私はmirkの答えにコメントすることはできませんので、別の答えをしなければなりません:

関数型プログラミングを可能にするために、標準にタプルが追加されたと思います。例として、

void my_func(const MyClass& input, MyClass& output1, MyClass& output2, MyClass& output3)
{
   // whatever
}

関数によって複数のオブジェクトが返される唯一の方法であるため、これは従来のC++のいたるところにあります。これは関数型プログラミングの忌み嫌いです。今、あなたは書くことができます

Tuple<MyClass, MyClass, MyClass> my_func(const MyClass& input)
{
   // whatever
   return Tuple<MyClass, MyClass, MyClass>(output1, output2, output3);
}

したがって、副作用と可変性を回避し、パイプライン化を可能にし、同時に、関数のセマンティック強度を保持する機会があります。

1
julio

タプルを使用する他のプログラミング言語との相互運用、および呼び出し側に余分な型を理解させることなく複数の値を返す。これらが私の頭に浮かぶ最初の2つです。

1
John Zwinck