「タプル」と「タイ」を介して比較演算子を実装することは良い考えですか?


98

(注:tupleおよびtieBoostまたはC ++ 11から取得できます。)厳密な弱い順序付けのように、すべての重要なものがそのデータ型に対してすでに行われているため
、2つの要素のみを含む小さな構造体を書き込むときstd::pairoperator<
ただし、欠点は、ほとんど役に立たない変数名です。私がそれを作成したtypedefとしても、特に両方が同じタイプである場合は特に、2日後に何が何firstであったかsecond正確には覚えていません。これは、3つ以上のメンバーの場合はさらに悪化します。これは、入れ子にするpairことでほとんど問題になります。
そのための他のオプションはtuple、BoostまたはC ++ 11のいずれかからですが、実際には見た目が良く、明確ではありません。そこで、必要な比較演算子を含め、自分で構造体を書くことに戻ります。
特にはoperator<非常に扱いにくい場合があるので、次のように定義された操作に依存するだけで、この混乱全体を回避することを考えましたtuple

の例operator<、例えば厳密な弱い順序付け:

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

tieは、渡された引数からtupleT&参照を作成します。)


編集:@DeadMGからプライベートに継承するという提案tupleは悪いことではありませんが、かなりの欠点がありました。

  • 事業者が独立している場合(おそらく友人)、私は公に継承する必要があります
  • キャストを使用すると、関数/演算子(operator=特に)を簡単にバイパスできます
  • tie解決策により、注文に関係のない特定のメンバーを除外することができます

この実装に考慮する必要がある欠点はありますか?


1
私には完全に合理的に見えます...
ildjarn

1
それはうまくいかなくても、それは非常に賢いアイデアです。これを調査する必要があります。
templatetypedef

これはかなり合理的に見えます。私が今考えることができる唯一の落とし穴は、tieビットフィールドメンバーに適用できないことです。
伊勢藤

4
私はこのアイデアが好きです!tie(...)呼び出しがさまざまな演算子(=、==、<など)で複製される場合は、プライベートインラインメソッドmake_tuple(...)を記述してそれをカプセル化し、次のように他のさまざまな場所から呼び出すことができますreturn lhs.make_tuple() < rhs.make_tuple();(ただし、その方法は宣言するのが楽しいかもしれません!)
aldo

13
@aldo:C ++ 14が助けに!auto tied() const{ return std::tie(the, members, here); }
Xeo 2014

回答:


60

これは確かに、自分でローリングするよりも正しい演算子を書くのを容易にするでしょう。プロファイリングで比較操作がアプリケーションの時間のかかる部分であることがわかった場合にのみ、別のアプローチを検討すると思います。それ以外の場合は、これを維持することの容易さが、考えられるパフォーマンスの懸念を上回ります。


17
私はケースを想像することはできませんtuple<>のが、operator<手書きのものより任意の遅くなるだろうし。
ildjarn、2011年

51
私はこれとまったく同じアイデアを得て、いくつかの実験を行いました。コンパイラーがタプルと参照に関連するすべてのものをインライン化および最適化し、手書きのコードとほぼ同じアセンブリを出力することを確認して、前向きに驚きました。
JohannesD

7
@JohannesD:私はその証言を支持することができ、同じことを一度行いました
sehe

これは厳密な弱い順序付けを保証しますか?どうやって?
CinCout

5

私はこれと同じ問題に遭遇し、私のソリューションはc ++ 11可変テンプレートを使用しています。これがコードです:

.hの部分:

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

引数なしの基本ケースの.cpp:

bool lexiLessthan()
{
  return false;
}

今あなたの例は次のようになります:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);

ここに同様のソリューションを入れましたが、!=演算子は必要ありません。 stackoverflow.com/questions/11312448/…–
steviekm3

3

私の意見では、std::tupleソルブと同じ問題にはまだ対処していません。つまり、各メンバー変数の数と名前の両方を知っている必要があり、関数内でそれを2回複製しています。private継承を選択できます。

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

このアプローチは、最初は少し厄介ですが、オーバーロードするすべての演算子のすべての場所ではなく、変数と名前を1か所で管理しているだけです。


3
だから私はT& one_member(){ return std::get<0>(*this); }等のような変数への名前付きアクセサーを使用しているでしょうか?しかし、constバージョンとnon-constバージョンのオーバーロードを含め、私が持っている「メンバー」ごとにそのようなメソッドを提供する必要はありませんか?
Xeo

@Xeo名前付きアクセサーは、実際の変数を作成する以上の作業を必要とするとは思わない。どちらの方法でも、変数ごとに個別の名前を付ける必要があります。const / non-constの重複があると思います。ただし、このすべての作業をテンプレート化できます。
Lee Louviere、2011年

1

複数の演算子オーバーロード、またはタプルからのメソッドを使用する場合は、タプルをクラスのメンバーにするか、タプルから派生させることをお勧めします。そうでなければ、あなたがしていることはもっと多くの仕事です。両者の間に決定する際には、解答への重要な質問は:あなたは、あなたのクラスがしたいかなるタプル?そうでない場合は、タプルを含め、委任を使用してインターフェースを制限することをお勧めします。

タプルのメンバーの「名前を変更」するアクセサーを作成できます。


私が意味するものとしてOPの質問を読んで「私のクラスを実装しているoperator<使用してstd::tie、合理的な?」この回答がその質問にどのように関連しているかはわかりません。
ildjarn、2011年

@ildjarnここに投稿しなかったコメントがいくつかあります。読みやすくするためにすべてをコンパイルしました。
Lee Louviere、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.