Реализация операторов сравнения через «кортеж» и «галстук» - хорошая идея?

98

(Примечание: tupleи tieможет быть взято из Boost или C ++ 11.)
При написании небольших структур только с двумя элементами я иногда склоняюсь к выбору a std::pair, поскольку все важные вещи уже сделаны для этого типа данных, например, operator<для строгого-слабого упорядочения .
Минусами являются бесполезные имена переменных. Даже если бы я сам это создал typedef, через 2 дня я не вспомню, что firstи что secondименно было, особенно если они оба одного типа. Ситуация становится еще хуже для более чем двух участников, поскольку вложение - pairотстой.
Другой вариант для этого -tupleлибо из Boost, либо из C ++ 11, но на самом деле это не выглядит лучше и понятнее. Поэтому я сам возвращаюсь к написанию структур, включая все необходимые операторы сравнения.
Поскольку в особенности это operator<может быть довольно громоздко, я подумал о том, чтобы обойти весь этот беспорядок, просто полагаясь на операции, определенные для tuple:

Пример operator<, например, для строгого-слабого упорядочивания:

bool operator<(MyStruct const& lhs, MyStruct const& rhs){
  return std::tie(lhs.one_member, lhs.another, lhs.yet_more) <
         std::tie(rhs.one_member, rhs.another, rhs.yet_more);
}

( tieДелает tupleиз T&ссылок из переданных аргументов.)


Изменить : предложение @DeadMG о частном наследовании от tupleне является плохим, но у него есть некоторые недостатки:

  • Если операторы автономные (возможно, друзья), мне нужно наследовать публично
  • С кастингом мои функции / операторы (в operator=частности) можно легко обойти
  • С помощью tieрешения я могу исключить некоторых участников, если они не имеют значения для заказа.

Есть ли в этой реализации недостатки, которые мне нужно учитывать?

Xeo
источник
1
Мне кажется, это совершенно разумно ...
ildjarn 02
1
Это очень умная идея, даже если она не сработает. Я собираюсь расследовать это.
templatetypedef
Это выглядит довольно разумно. Единственная ошибка, о которой я могу думать сейчас, - это то, что она tieне может быть применена к членам битового поля.
Ise Wisteria
4
Мне нравится эта идея! Если tie(...)вызовы будут дублироваться в различных операторах (=, ==, <и т. Д.), Вы можете написать частный встроенный метод, make_tuple(...)чтобы инкапсулировать его, а затем вызывать его из различных других мест, например return lhs.make_tuple() < rhs.make_tuple();(хотя возвращаемый тип из этот метод было бы забавно объявить!)
aldo
13
@aldo: C ++ 14 спешит на помощь! auto tied() const{ return std::tie(the, members, here); }
Xeo

Ответы:

61

Это, безусловно, упростит написание правильного оператора, чем его самостоятельно. Я бы посоветовал рассмотреть другой подход только в том случае, если профилирование показывает, что операция сравнения занимает много времени в вашем приложении. В противном случае простота обслуживания должна перевесить любые возможные проблемы с производительностью.

Марк Б
источник
17
Я не могу представить себе случай , когда tuple<>«s operator<будет медленнее , чем рукописные один.
ildjarn 02
51
Однажды мне пришла в голову точно такая же идея, и я поэкспериментировал. Был приятно удивлен, увидев, что компилятор встроил и оптимизировал все, что связано с кортежами и ссылками, создавая сборку, почти идентичную рукописному коду.
JohannesD
7
@JohannesD: Я могу поддержать это свидетельство, однажды сделал то же самое
см.
Гарантирует ли это строгое слабое упорядочивание ? Как?
CinCout
5

Я столкнулся с этой же проблемой, и в моем решении используются вариативные шаблоны С ++ 11. Вот код:

Часть .h:

/***
 * Generic lexicographical less than comparator written with variadic templates
 * Usage:
 *   pass a list of arguments with the same type pair-wise, for intance
 *   lexiLessthan(3, 4, true, false, "hello", "world");
 */
bool lexiLessthan();

template<typename T, typename... Args>
bool lexiLessthan(const T &first, const T &second, Args... rest)
{
  if (first != second)
  {
    return first < second;
  }
  else
  {
    return lexiLessthan(rest...);
  }
}

И .cpp для базового случая без аргументов:

bool lexiLessthan()
{
  return false;
}

Теперь ваш пример становится:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
user2369060
источник
Я использовал аналогичное решение, но не требовал оператора! =. stackoverflow.com/questions/11312448/…
steviekm3
3

На мой взгляд, вы по-прежнему не std::tupleрешаете ту же проблему, что и решает, а именно, вы должны знать как количество, так и имя каждой переменной-члена, вы дважды дублируете ее в функции. Вы можете выбрать privateнаследование.

struct somestruct : private std::tuple<...> {
    T& GetSomeVariable() { ... }
    // etc
};

Этот подход для начала немного беспорядок, но вы только сохраняете переменные и имена в одном месте, а не в каждом месте для каждого оператора, который хотите перегрузить.

Щенок
источник
3
Итак, я бы использовал именованные средства доступа к таким переменным, как T& one_member(){ return std::get<0>(*this); }etc? Но разве мне не нужно было бы предоставлять такой метод для каждого «члена», который у меня есть, включая перегрузки для константной и неконстантной версии?
Xeo 02
@Xeo Я не считаю, что именованные аксессоры требуют больше работы, чем создание реальных переменных. В любом случае у вас должно быть отдельное имя для каждой переменной. Я предполагаю, что будет дублирование для const / non-const. Однако вы можете шаблонизировать всю эту работу.
Ли Лувьер
1

Если вы планируете использовать более одной перегрузки оператора или несколько методов из кортежа, я бы рекомендовал сделать кортеж членом класса или производным от кортежа. В противном случае то, что вы делаете, - это намного больше работы. При выборе между этими двумя, один важный вопрос , чтобы ответить: Вы хотите , чтобы ваш класс будет кортеж? В противном случае я бы рекомендовал содержать кортеж и ограничить интерфейс с помощью делегирования.

Вы можете создать средства доступа для «переименования» членов кортежа.

Ли Лувьер
источник
Я прочитал на вопрос OP как означающее «реализует свой класс с operator<помощью std::tieразумного?» Я не понимаю, как этот ответ соотносится с этим вопросом.
ildjarn 02
@ildjarn Есть некоторые комментарии, которые я не размещал здесь. Я скомпилировал все, чтобы лучше читалось.
Ли Лувьер