Реализация операторов сравнения через "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 членом класса или получить из кортежа. В противном случае, вы делаете гораздо больше работы. При принятии решения между ними важно ответить на важный вопрос: хотите ли вы, чтобы ваш класс был кортежем? Если нет, я бы рекомендовал содержать кортеж и ограничивать интерфейс, используя делегирование.
Вы можете создать аксессоров для "переименования" элементов кортежа.