Оператор <сравнение нескольких полей
У меня есть следующий оператор < который должен сортировать сначала по значению, затем по другому значению:
inline bool operator < (const obj& a, const obj& b)
{
if(a.field1< b.field1)
return true;
else
return a.field2 < b.field2;
}
У меня такое чувство, что это неправильно, и вы не можете этого сделать без другого третьего теста comparaison для переменных-членов, но я не могу найти ни одного примера, где это не работает.
Так что это действительно так, как ожидалось?
спасибо
изменить:
Я бы закодировал его как:
inline bool operator < (const obj& a, const obj& b)
{
if(a.field1< b.field1)
return true;
else if(a.field1> b.field1)
return false;
else
return a.field2 < b.field2;
}
Есть ли какие-то различия? Я спрашиваю, потому что я знаю, что моя правильно из опыта, но и дольше, чем первая.
Ответы
Ответ 1
Я хотел бы сделать все это сам.
Вы должны сравнивать значения Obj::field2
, если значения Obj::field1
равны.
Простой способ понять:
/* This will meet the requirements of Strict-Weak-Ordering */
if (a.field1 != b.field1) return a.field1 < b.field1;
else return a.field2 < b.field2;
Правильный (рекомендуемый) способ:
"Правильный" способ его реализации использует только operator<
для сравнения полей, ниже выглядит более сложным, чем это действительно есть.
Однако он даст тот же результат, что и ранее понятный пример.
return a.field1 < b.field1 || (
!(b.field1 < a.field1) && a.field2 < b.field2
);
Должен быть способ реализации operator<
, не вызывая много головной боли?
С++ 11
Вы можете использовать std::tuple
из STL, у которого уже есть operator<
для нескольких определенных полей, например, в приведенном ниже примере.
#include <utility>
...
inline bool
operator< (Obj const& lhs, Obj const& rhs)
{
return std::tie (lhs.field1, lhs.field2) < std::tie (rhs.field1, rhs.field);
}
С++ 03
Если у вашего компилятора еще нет поддержки для С++ 11, и вам нужно сравнить только два поля от каждого объекта, который вы могли бы использовать std::pair
.
Причина std::make_pair
такая же, как в предыдущем примере, используя std::tie
.
#include <utility>
...
inline bool
operator< (Obj const& lhs, Obj const& rhs)
{
return std::make_pair (lhs.field1, lhs.field2)
< std::make_pair (rhs.field1, rhs.field2);
}
с помощью std::pair
потребуются копии создаваемых элементов, что в некоторых случаях нежелательно.
Это действительно рекомендуемая практика?
См. ниже вопросы/ответы для получения дополнительной информации, но подведите итог; подход С++ 11 не вызывает много накладных расходов и очень прост в реализации.
Ответ 2
Подумайте, что произойдет, если a.field1
больше, чем b.field1
, но a.field2
меньше, чем b.field2
. В этих обстоятельствах вы сравниваете исключительно на основе field2
, а это не то, что вам нужно.
Вам нужно только ввести field2
в игру, когда поля field1
равны, поэтому вы ищете что-то вроде (псевдокод):
if a.field1 < b.field1: return true
if a.field1 > b.field1: return false
# field1s is equal here.
return a.field2 < b.field2
Ответ 3
Нет. Вам также нужно уловить (a.field1 > b.field1)
.
Это не строгий слабый порядок, потому что он даст (1,2) < (2,1)
, но также (2,1) < (1,2)
.
Ответ 4
Здесь версия, использующая правило логического короткого замыкания, чтобы избежать явного ветвления
template<typename T>
bool operator< (T const& a, T const& b)
{
return (
( a.field1 < b.field1 ) || (( a.field1 == b.field1 ) &&
( a.field2 < b.field2 ))
);
}
Это предполагает, что ваш примитивный тип field1
имеет operator==
. Это утомительно, чтобы ввести это для более чем двух полей, но вы можете использовать std::lexicographical_compare
, если ваш класс obj
хранит поля внутри std::array<T, N>
для некоторого типа T
и size N
template<typename T, int N>
struct obj
{
std::array<T, N> field;
};
bool operator< (obj const& a, T const& b)
{
return std::lexicographical_compare(
a.field.begin(), a.field.end(),
b.field.begin(), b.field.end()
);
}
Обратите внимание, что есть проект документа N3326, в котором обсуждается возможность добавления операторов ==
и <
автоматически для типов классов.
Ответ 5
Мой метод, описанный ниже, включает в себя несколько макросов, но все же полезен во многих случаях. Может быть, что-то подобное можно сделать с помощью встроенных функций.
#define CMP_LT2(a, b) ((a) < (b) ? (a) : (b))
#define CMP_GT2(a, b) ((a) > (b) ? (a) : (b))
#define CMP_LTE2(a, b) ((a) <= (b) ? (a) : (b))
#define CMP_GTE2(a, b) ((a) >= (b) ? (a) : (b))
#define CMP_EQ2(a, b) ((a) == (b))
#define CMP_NEQ2(a, b) ((a) != (b))
#define CMP_LT3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_LT2(a, b))
#define CMP_GT3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_GT2(a, b))
#define CMP_LTE3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_LT2(a, b))
#define CMP_GTE3(a, b, c) (CMP_EQ2(a, b) ? (c) : CMP_GT2(a, b))
#define CMP_EQ3(a, b, c) ((a) == (b) ? (c) : false)
#define CMP_NEQ3(a, b, c) ((a) != (b) ? true : (c))
Тогда предположим, что у вас есть:
struct Point3D {
double x;
double y;
double z;
};
А потом ты пишешь:
struct Point3D {
double x;
double y;
double z;
bool operator<(const Point3D& other) const noexcept
{
return CMP_LT3(z, other.z,
CMP_LT3(y, other.y,
CMP_LT2(x, other.x)));
}
};
Ответ 6
Вы можете использовать переменные шаблоны в С++ 11 или более поздних версиях
template<typename T>
bool less_than( const T& a, const T& b )
{
return a < b;
}
template<typename T, typename... Args>
bool less_than( const T& a, const T& b, Args... args )
(
if ( a < b )
return true;
else if ( b < a )
return false;
else
return less_than( args... );
)
Тогда вы бы позвонили как
return less_than(a.x,b.x,
a.y,b.y,
a.z,b.z);
Он поддерживает любое количество полей или типов, если тип имеет & lt; оператор. Вы можете смешивать типы.