Оператор <сравнение нескольких полей

У меня есть следующий оператор < который должен сортировать сначала по значению, затем по другому значению:

    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; оператор. Вы можете смешивать типы.