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.
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.
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.
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
Dans ce cas, vous pouvez utiliser boost::Tuple<int, int, int>
- son opérateur < fonctionne exactement comme vous le souhaitez.
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;
}
};
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
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;
}
...
}
#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;
}
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 ..
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?
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";
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
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;
}
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.