использование make_tuple для сравнения

Возможный дубликат:
Реализовать операторы сравнения через 'tuple' и 'tie', хорошая идея?

иногда мне нужно написать несколько уродливых функторов
например.

lhs.date_ < rhs.date_ ||
lhs.date_ == rhs.date_ && lhs.time_ < rhs.time_ ||
lhs.date_ == rhs.date_ && lhs.time_ == rhs.time_ && lhs.id_ < rhs.id_ .....

меня это очень раздражало.
Поэтому я начал избегать написанного ниже:

std::make_tuple( lhs.date_, lhs.time_, lhs.id_ ) < 
    std::make_tuple(rhs.date_, rhs.time_, rhs.id_ );

и я почти счастлив, но то, что я, вероятно, использую кортежи не по их назначению, заставляет меня беспокоиться.

Не могли бы вы покритиковать это решение?
Или это хорошая практика?
Как избежать подобных сравнений?

ОБНОВЛЕНИЕ:
Спасибо, что указали на std::tie, чтобы избежать копирования объектов.
И спасибо, что указали на повторяющийся вопрос


person bayda    schedule 29.05.2012    source источник
comment
std::make_tuple делает копии объектов, которые вы ему передаете (если вы не используете std::ref или std::cref) -- чтобы сравнить существующие объекты без создания копий, используйте std::forward_as_tuple или std::tie вместо std::make_tuple.   -  person ildjarn    schedule 30.05.2012
comment
Если вместо этого вы используете std::tie, это очень хорошая практика, так как оператор‹ очень легко правильно реализовать.   -  person Jonathan Wakely    schedule 30.05.2012


Ответы (2)


В объявлении операторов сравнения std::tuple указано, что:

Сравнивает левый и правый лексикографически, то есть сравнивает первые элементы, если они эквивалентны, сравнивает вторые элементы, если они эквивалентны, сравнивает третьи элементы и так далее.

Итак, то, что вы делаете, кроме возможности создавать ненужные временные файлы (вероятно, оптимизированные), мне кажется нормальным.

Обратите внимание, что эквивалент означает !( lhs < rhs ) && !( rhs < lhs ), а не lhs == rhs, это равенство. Предполагая, что эквивалент и равенство означают одно и то же для вашего класса, все будет в порядке. Обратите внимание, что это ничем не отличается, например, от доступа к set/map по ключу.

Чтобы избежать временных, вы можете использовать std::tie, который создает кортеж из ссылок lvalue на свои аргументы:

std::tie( lhs.date_, lhs.time_, lhs.id_ ) < 
    std::tie( rhs.date_, rhs.time_, rhs.id_ );

Аналогичным образом std::forward_as_tuple создает кортеж из ссылок rvalue.

person K-ballo    schedule 29.05.2012
comment
Эквивалент IIRC означает ‹= && ›=. Так что с кортежем есть риск, но он, вероятно, безопасен в ситуации с ОП. - person djechlin; 30.05.2012
comment
@djechlin: эквивалент означает !( lhs < rhs ) && !( rhs < lhs ), что эквивалентно тому, что вы написали. Почему это риск? - person K-ballo; 30.05.2012
comment
Спасибо. Так что OP должен знать, что это не то же самое, что он делал, используя ==. Я не знаю, как реализован класс даты OP, но мне кажется, что == будет соответствовать дате, а ‹ или › — секунде. - person djechlin; 30.05.2012
comment
@djechlin: обновил мой ответ, спасибо. - person K-ballo; 30.05.2012
comment
Ради эффективности я бы по крайней мере упомянул std::tie или std::forward_as_tuple. - person ildjarn; 30.05.2012
comment
@ildjarn: std::tie! Это то, что я искал... Я решил, что явные ссылочные аргументы на make_tuple не сработают. - person K-ballo; 30.05.2012

Возможно, вам лучше использовать tie вместо make_tuple (в зависимости от того, стоит ли избегать копирования/перемещения членов), но в остальном это будет работать, и в этом нет ничего страшного.

Если подумать, многие структуры POD в основном являются «именованными кортежами», и C++ фактически обслуживает это, предлагая сравнение на равенство по элементам (а также копирование, перемещение и т. д.) — и в большинстве таких случаев расширяя его до ( лексикографическое) сравнение по члену также имеет смысл. Но, к сожалению, C++ не предоставляет никакого способа указать лексикографическое сравнение членов, кроме как путем его явного написания. Конечно, в языке с интроспекцией это было бы довольно легко добавить, но в C++ это не так.

Один очевидный ответ состоит в том, чтобы на самом деле сделать ваш класс именованным кортежем. Вместо членов int date_, int64_t time_ и string id_ наследуйте от кортежа и напишите методы доступа, которые скрывают кортежность от ваших пользователей. Но в этом случае вы только что поменяли уродство в реализации сравнения на уродство в реализации аксессора, и неясно, что лучше.

Вы можете использовать макросы, чтобы скрыть уродство, но это, вероятно, не намного лучше:

DEFINE_NAMED_TUPLE_CLASS(MyDate, int date, int64_t time, string id)
  ...
END_NAMED_TUPLE_CLASS

Или вы можете создать свой собственный внешний генератор кода, который позволит вам предварительно обработать расширенный C++, который имеет "named_tuple_class", который он обрабатывает, просто изменив тег на "class" и затем определив операторы сравнения. Но это много работы.

person abarnert    schedule 29.05.2012