Реализация операторов сравнения через "tuple" и "tie" - хорошая идея?

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

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

Ответ 1

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

Ответ 2

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

Часть .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
);

Ответ 3

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

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

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

Ответ 4

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

Вы можете создать аксессоров для "переименования" элементов кортежа.