web-dev-qa-db-fra.com

Définition de l'opérateur <pour une structure

J'utilise parfois de petits structs comme clés dans les cartes, et je dois donc leur définir un operator<. Habituellement, cela finit par ressembler à ceci:

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;
    }
};

Cela semble terriblement verbeux et sujet aux erreurs. Existe-t-il un meilleur moyen ou un moyen simple d'automatiser la définition de operator< Pour un struct ou class?

Je sais que certaines personnes aiment simplement utiliser quelque chose comme memcmp(this, &rhs, sizeof(MyStruct)) < 0, mais cela peut ne pas fonctionner correctement s'il y a des octets de remplissage entre les membres, ou s'il y a des tableaux de chaînes char qui peuvent contenir des ordures après les terminateurs nuls.

53

C'est une question assez ancienne et par conséquent toutes les réponses ici sont obsolètes. C++ 11 permet une solution plus élégante et efficace:

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);
}

Pourquoi est-ce mieux que d'utiliser boost::make_Tuple? Car make_Tuple créera des copies de tous les membres de données, ce qui peut être coûteux. std::tie , en revanche, ne fera que créer une fine enveloppe de références (que le compilateur optimisera probablement complètement).

En fait, le code ci-dessus doit maintenant être considéré comme la solution idiomatique pour implémenter une comparaison lexicographique pour les structures avec plusieurs membres de données.

91
Konrad Rudolph

D'autres ont mentionné boost::Tuple, Ce qui vous donne une comparaison lexicographique. Si vous souhaitez le conserver en tant que structure avec des éléments nommés, vous pouvez créer des tuples temporaires pour comparaison:

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);
}

En C++ 0x, cela devient std::make_Tuple().

MISE À JOUR: Et maintenant C++ 11 est là, il devient std::tie(), pour faire un Tuple de références sans copier les objets. Voir la nouvelle réponse de Konrad Rudolph pour plus de détails.

18
Mike Seymour

Je ferais ceci:

#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

Dans ce cas, vous pouvez utiliser boost::Tuple<int, int, int> - son opérateur < fonctionne exactement comme vous le souhaitez.

6
Steve Townsend

Je pense que le moyen le plus simple est de s'en tenir à l'opérateur <pour toutes les comparaisons et de ne pas utiliser> ou ==. Vous trouverez ci-dessous le modèle que je suis, et vous pouvez suivre pour toutes vos structures

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

La meilleure façon que je connaisse est d'utiliser un boost Tuple . Il offre entre autres une comparaison et des constructeurs intégrés.

#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 )
   ...

J'aime aussi Mike Seymors suggestion d'utiliser des tuples temporaires via le make_Tuple de boost

3
Peter G.

J'implémente généralement l'ordre lexicographique de cette façon:

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
    }
    ...
}

Attention, il faut une considération supplémentaire pour les valeurs à virgule flottante (avertissements G ++), pour ceux quelque chose comme ça serait mieux:

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

si vous ne pouvez pas utiliser boost, vous pouvez essayer quelque chose comme:

#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;
}

Je suppose que cela évite toutes les macros, et tant que les types de la structure prennent en charge <, cela devrait fonctionner. Bien sûr, il y a des frais généraux pour cette approche, la construction de branches is_gt puis superflous pour chaque paramètre si l'une des valeurs est supérieure ...

Modifier:

Modifiée en fonction des commentaires, cette version devrait désormais également être court-circuitée, utilise désormais deux booléens pour conserver l'état (pas sûr qu'il existe un moyen de le faire avec un seul booléen).

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;
};

et

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);
}

il suffit de constituer une collection de ces foncteurs pour diverses comparaisons ..

2
Nim

Je viens d'apprendre le boost::Tuple astuce, merci, @Mike Seymour!

Si vous ne pouvez pas vous permettre Boost, mon idiome préféré est:

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);
}

ce que j'aime parce qu'il met tout en structure parallèle qui rend les erreurs et omissions plus faciles à repérer.

Mais, bien sûr, vous testez cette unité de toute façon, non?

1
mskfisher

J'ai écrit un script Perl pour m'aider. Par exemple donné:

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

Il émettrait:

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;
}

Code (c'est un peu long):

#!/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

Lorsque vous pouvez produire des itérateurs sur les éléments définissant l'ordre lexicographique, vous pouvez utiliser std::lexicographic_compare, À partir de <algorithm>.

Sinon, je suggère de baser les comparaisons sur les anciennes fonctions de comparaison à trois valeurs, par ex. comme suit:

#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;
}

J'ai inclus les derniers if et return dans la fonction compare juste pour la généralité. J'imagine que cela peut aider la maintenance à adhérer de manière très rigide à un seul système. Sinon, vous pourriez simplement y faire une return compared( lhs.c, rhs.c ) (et peut-être vous préférez cela).

Santé et hth.,

- Alf

0
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

Si les comparaisons à trois voies sont plus coûteuses que les deux voies, et si les parties les plus significatives des structures sont souvent égales, il peut être utile de définir des fonctions de comparaison de champ avec un paramètre de "biais", de sorte que si "biais" est faux, ils retourneront vrai quand a> b, et quand le biais est vrai, ils retourneront vrai si a> = b. Ensuite, on peut savoir si a> b en faisant quelque chose comme:

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

Notez que toutes les comparaisons seront effectuées, même si a.f1 <> b.f1, mais les comparaisons seront bidirectionnelles au lieu de trois voies.

0
supercat