web-dev-qa-db-ja.com

構造体のoperator <の定義

私は時々小さなstructsをマップのキーとして使用するため、それらに対して_operator<_を定義する必要があります。通常、これは次のようになります。

_struct MyStruct
{
    A a;
    B b;
    C c;

    bool operator<(const MyStruct& rhs) const
    {
        if (a < rhs.a)
        {
           return true;
        }
        else if (a == rhs.a)
        {
            if (b < rhs.b)
            {
                return true;
            }
            else if (b == rhs.b)
            {
                return c < rhs.c;
            }
        }

        return false;
    }
};
_

これは非常に冗長でエラーが発生しやすいようです。 structまたはclassの_operator<_の定義を自動化するより良い方法、または簡単な方法はありますか?

memcmp(this, &rhs, sizeof(MyStruct)) < 0のようなものを使用したい人もいますが、メンバー間にパディングバイトがある場合、またはchar文字列配列にゴミが含まれている場合、これは正しく機能しない可能性がありますnullターミネーター。

53

これは非常に古い質問であり、結果としてここでのすべての回答は時代遅れです。 C++ 11は、よりエレガントで効率的なソリューションを可能にします。

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

なぜboost::make_Tupleを使用するよりも優れているのですか? make_Tupleはすべてのデータメンバーのコピーを作成するため、コストがかかる可能性があります。 std::tie は、対照的に、参照の薄いラッパーを作成するだけです(コンパイラはおそらく完全に最適化されます)。

実際、上記のコードは、いくつかのデータメンバーを持つ構造の辞書編集比較を実装するための慣用的なソリューションと見なされるようになりました。

91
Konrad Rudolph

他の人は_boost::Tuple_に言及していますが、これは辞書式比較を提供します。名前付き要素を持つ構造体として保持する場合は、比較のために一時的なタプルを作成できます。

_bool operator<(const MyStruct& x, const MyStruct& y)
{
    return boost::make_Tuple(x.a,x.b,x.c) < boost::make_Tuple(y.a,y.b,y.c);
}
_

C++ 0xでは、これはstd::make_Tuple()になります。

更新:そしてC++ 11がここにあり、オブジェクトをコピーせずに参照のタプルを作成するためにstd::tie()になります。詳細については、Konrad Rudolphの新しい回答を参照してください。

18
Mike Seymour

私はこれをします:

#define COMPARE(x) if((x) < (rhs.x)) return true; \
                   if((x) > (rhs.x)) return false;
COMPARE(a)
COMPARE(b)
COMPARE(c)
return false;
#undef COMPARE
9
Benoit

この場合、boost::Tuple<int, int, int>を使用できます。その operator < は、希望どおりに機能します。

6
Steve Townsend

最も簡単な方法は、すべての比較で<演算子を使用し、>または==を使用しないことだと思います。以下は私が従うパターンであり、すべての構造体について従うことができます

typedef struct X
{
    int a;
    std::string b;
    int c;
    std::string d;

    bool operator <( const X& rhs ) const
    {
        if (a < rhs.a) { return true; }
        else if ( rhs.a < a ) { return false; }

        // if neither of the above were true then 
        // we are consdidered equal using strict weak ordering
        // so we move on to compare the next item in the struct

        if (b < rhs.b) { return true; }
        if ( rhs.b < b ) { return false; }

        if (c < rhs.c) { return true; }
        if ( rhs.c < c ) { return false; }

        if (d < rhs.d) { return true; }
        if ( rhs.d < d ) { return false; }

        // if both are completely equal (based on strict weak ordering)
        // then just return false since equality doesn't yield less than
        return false;
    }
};
4
bjackfly

私が知っている最良の方法は、 boost Tuple を使用することです。特に、組み込みの比較とコンストラクターを提供します。

#include <boost/Tuple/tuple.hpp>
#include <boost/Tuple/tuple_comparison.hpp>

typedef boost::Tuple<int,int,int> MyStruct;

MyStruct x0(1,2,3), x1(1,2,2);
if( x0 < x1 )
   ...

Mike Seymorsも好きです boostのmake_Tupleで一時タプルを使用する提案

3
Peter G.

私は通常、このように辞書式順序を実装します。

bool operator < (const MyObject& obj)
{
    if( first != obj.first ){
        return first < obj.first;
    }
    if( second != obj.second ){
        return second < obj.second;
    }
    if( third != obj.third ){
        return third < obj.third
    }
    ...
}

浮動小数点値(G ++の警告)には特別な配慮が必要であることに注意してください。次のようなものの方が良いでしょう。

bool operator < (const MyObject& obj)
{
    if( first < obj.first ){
        return true;
    }
    if( first > obj.first ){
        return false;
    }
    if( second < obj.second ){
        return true;
    }
    if( second > obj.second ){
        return false;
    }
    ...
}
3
Frigo
#include <iostream>

#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/less.hpp>

struct MyStruct {
   int a, b, c;
};

BOOST_FUSION_ADAPT_STRUCT( MyStruct,
                           ( int, a )
                           ( int, b )
                           ( int, c )
                          )

bool operator<( const MyStruct &s1, const MyStruct &s2 )
{
   return boost::fusion::less( s1, s2 );
}

int main()
{
   MyStruct s1 = { 0, 4, 8 }, s2 = { 0, 4, 9 };
   std::cout << ( s1 < s2 ? "is less" : "is not less" ) << std::endl;
}
2
usta

ブーストを使用できない場合は、次のようなものを試すことができます。

#include <iostream>

using namespace std;

template <typename T>
struct is_gt
{
  is_gt(const T& l, const T&r) : _s(l > r) {}

  template <typename T2>
  inline is_gt<T>& operator()(const T2& l, const T2& r)
  {
    if (!_s)
    {
      _s = l > r;
    }
    return *this;
  }

  inline bool operator!() const { return !_s; }

  bool _s;
};

struct foo
{
  int a;
  int b;
  int c;

  friend bool operator<(const foo& l, const foo& r);
};

bool operator<(const foo& l, const foo& r)
{
  return !is_gt<int>(l.a, r.a)(l.b, r.b)(l.c, r.c);
}

int main(void)
{
  foo s1 = { 1, 4, 8 }, s2 = { 2, 4, 9 };
  cout << "s1 < s2: " << (s1 < s2) << endl;
  return 0;
}

これによりマクロが回避され、構造体の型が<をサポートしている限り、動作するはずです。もちろん、このアプローチにはオーバーヘッドがあり、is_gtを構築してから、値のいずれかが大きい場合は各パラメーターのスーパーフロースブランチを構築します...

編集:

コメントに基づいて変更され、このバージョンも同様に短絡する必要があり、2つのブールを使用して状態を保持します(単一のブールでこれを行う方法があるかどうかはわかりません)。

template <typename T>
struct is_lt
{
  is_lt(const T& l, const T&r) : _s(l < r), _e(l == r) {}

  template <typename T2>
  inline bool operator()(const T2& l, const T2& r)
  {
    if (!_s && _e)
    {
      _s = l < r;
      _e = l == r;
    }
    return _s;
  }

  inline operator bool() const { return _s; }

  bool _s;
  bool _e;
};

そして

bool operator<(const foo& l, const foo& r)
{
  is_lt<int> test(l.a, r.a);
  return test || test(l.b, r.b) || test(l.c, r.c);
}

さまざまな比較のために、このようなファンクターのコレクションを作成するだけです。

2
Nim

boost::Tupleトリック、ありがとう、@マイクシーモア!

Boostを購入する余裕がない場合、私のお気に入りのイディオムは次のとおりです。

bool operator<(const MyStruct& rhs) const
{
    if (a < rhs.a)  return true;
    if (a > rhs.a)  return false;

    if (b < rhs.b)  return true;
    if (b > rhs.b)  return false;

    return (c < rhs.c);
}

エラーと省略を見つけやすくする並列構造ですべてを設定するため、これが好きです。

しかし、もちろん、あなたはとにかくこれを単体テストしていますか?

1
mskfisher

私は私を助けるためにPerlスクリプトを書きました。以下に例を示します。

class A
{
int a;
int b;
int c;

以下を出力します:

bool operator<(const A& left, const A& right)
{
    bool result(false);

    if(left.a != right.a)
    {
        result = left.a < right.a;
    }
    else if(left.b != right.b)
    {
        result = left.b < right.b;
    }
    else
    {
        result = left.c < right.c;
    }

    return result;
}

コード(少し長い):

#!/usr/bin/Perl

use strict;

main:

my $line = <>;
chomp $line;
$line =~ s/^ *//;

my ($temp, $line, $temp) = split / /, $line;

print "bool operator<(const $line& left, const $line& right)\n{\n";
print "    bool result(false);\n\n";

my $ifText = "if";

$line = <>;

while($line)
{
    if($line =~ /{/)
    {
        $line = <>;
        next;
    }
    if($line =~ /}/)
    {
        last;
    }

    chomp $line;
    $line =~ s/^ *//;

    my ($type, $name) = split / /, $line;
    $name =~ s/; *$//;

    $line = <>;
    if($line && !($line =~ /}/))
    {
        print "    $ifText(left.$name != right.$name)\n";
        print "    {\n";
        print "        result = left.$name < right.$name;\n";
        print "    }\n";

        $ifText = "else if";
    }
    else
    {
        print "    else\n";
        print "    {\n";
        print "        result = left.$name < right.$name;\n";
        print "    }\n";

        last;
    }
}

print "\n    return result;\n}\n";
0
Mark B

辞書順を定義する要素に対して反復子を作成できる場合は、_std::lexicographic_compare_の_<algorithm>_を使用できます。

それ以外の場合は、古い3値比較関数に基づいて比較を行うことをお勧めします。次のように:

_#include <iostream>

int compared( int a, int b )
{
    return (a < b? -1 : a == b? 0 : +1);
}

struct MyStruct
{
    friend int compared( MyStruct const&, MyStruct const& );
    int a;
    int b;
    int c;

    bool operator<( MyStruct const& rhs ) const
    {
        return (compared( *this, rhs ) < 0);
    }
};

int compared( MyStruct const& lhs, MyStruct const& rhs )
{
    if( int x = compared( lhs.a, rhs.a ) ) { return x; }
    if( int x = compared( lhs.b, rhs.b ) ) { return x; }
    if( int x = compared( lhs.c, rhs.c ) ) { return x; }
    return 0;
}

int main()
{
    MyStruct const  s1 = { 0, 4, 8 };
    MyStruct const  s2 = { 0, 4, 9 };
    std::cout << ( s1 < s2 ? "is less" : "is not less" ) << std::endl;
}
_

一般的な目的で、最後のifreturncompare関数に含めました。メンテナンスが単一システムに非常に厳格に準拠するのに役立つと思います。それ以外の場合は、単にreturn compared( lhs.c, rhs.c )を実行できます(おそらくそれを好むでしょう)。

乾杯

−アルフ

bool operator <(const A& l, const A& r)
{

    int[] offsets = { offsetof(A, a), offsetof(A, b), offsetof(A, c) };
    for(int i = 0; i < sizeof(offsets)/sizeof(int); i++)
    {
        int ta = *(int*)(((const char*)&l)+offsets[i]);
        int tb = *(int*)(((const char*)&r)+offsets[i]);

        if (ta < tb)
             return true;
        else if (ta > tb)
             break;

    }
    return false;
}
0
nothrow

3者間比較が2者間よりも高価であり、構造のより重要な部分が頻繁に等しい場合は、「bias」パラメーターを使用してフィールド比較関数を定義すると便利です。 falseの場合、a> bの場合にtrueを返し、バイアスがtrueの場合、a> = bの場合にtrueを返します。次に、a> bかどうかを確認するには、次のようにします。

 return compare1(a.f1、b.f1、compare2(a.f2、b.f2、compare3(a.f3、b.f3、false))); 

A.f1 <> b.f1であっても、すべての比較が実行されますが、比較は3者間ではなく2者間で行われることに注意してください。

0
supercat