Implementar operadores de comparação por meio de 'tupla' e 'amarrar', uma boa ideia?

98

(Nota: tuplee tiepode ser obtido de Boost ou C ++ 11.)
Ao escrever pequenas estruturas com apenas dois elementos, às vezes tendo a escolher um std::pair, pois todas as coisas importantes já foram feitas para esse tipo de dados, como operator<para ordenação estrita-fraca .
As desvantagens, porém, são os nomes de variáveis ​​praticamente inúteis. Mesmo que eu mesmo tenha criado isso typedef, não vou me lembrar 2 dias depois o que firste o que secondexatamente foi, especialmente se ambos forem do mesmo tipo. Isso fica ainda pior para mais de dois membros, já que aninhar pairé uma droga.
A outra opção para isso é umtuple, tanto do Boost quanto do C ++ 11, mas isso não parece nem um pouco melhor e mais claro. Portanto, volto a escrever as estruturas sozinho, incluindo quaisquer operadores de comparação necessários.
Uma vez que, especialmente, o operator<pode ser bastante complicado, pensei em contornar toda essa bagunça apenas contando com as operações definidas para tuple:

Exemplo de operator<, por exemplo, para ordenação estrita-fraca:

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);
}

( tieFaz uma tupledas T&referências dos argumentos passados.)


Edit : A sugestão de @DeadMG para herdar de forma privada tuplenão é ruim, mas tem algumas desvantagens:

  • Se os operadores forem independentes (possivelmente amigos), preciso herdar publicamente
  • Com a transmissão, minhas funções / operadores ( operator=especificamente) podem ser facilmente contornados
  • Com a tiesolução, posso deixar de fora determinados membros se eles não importarem para o pedido

Há alguma desvantagem nessa implementação que preciso considerar?

Xeo
fonte
1
Parece perfeitamente razoável para mim ...
ildjarn
1
É uma ideia muito inteligente, mesmo que não dê certo. Vou ter que investigar isso.
templatetypedef
Isso parece bastante razoável. A única armadilha em que posso pensar agora é que tienão pode ser aplicada a membros de campo de bits.
Ise Wisteria
4
Eu gosto desta ideia! Se as tie(...)chamadas forem duplicadas em vários operadores (=, ==, <, etc.), você pode escrever um método inline privado make_tuple(...)para encapsular isso e, em seguida, chamá-lo de vários outros lugares, como em return lhs.make_tuple() < rhs.make_tuple();(embora o tipo de retorno de esse método pode ser divertido de declarar!)
aldo
13
@aldo: C ++ 14 para o resgate! auto tied() const{ return std::tie(the, members, here); }
Xeo

Respostas:

60

Isso certamente tornará mais fácil escrever um operador correto do que fazer você mesmo. Eu diria que apenas consideraria uma abordagem diferente se a criação de perfil mostrar que a operação de comparação é uma parte demorada de seu aplicativo. Caso contrário, a facilidade de manutenção deve superar qualquer preocupação de desempenho possível.

Mark B
fonte
17
Não consigo imaginar um caso em que tuple<>'s operator<seria mais lento do que um escrito à mão.
ildjarn
51
Tive esta mesma ideia uma vez e fiz algumas experiências. Fiquei positivamente surpreso ao ver que o compilador inline e otimiza tudo o que tem a ver com tuplas e referências, emitindo assembly quase idêntico ao código escrito à mão.
JohannesD
7
@JohannesD: Posso apoiar esse testemunho, fiz o mesmo uma vez
ver
Isso garante um pedido estritamente fraco ? Quão?
CinCout
5

Eu me deparei com esse mesmo problema e minha solução usa modelos variadic c ++ 11. Aí vem o código:

A parte .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...);
  }
}

E o .cpp para o caso base sem argumentos:

bool lexiLessthan()
{
  return false;
}

Agora seu exemplo se torna:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
user2369060
fonte
Eu coloquei uma solução semelhante aqui, mas não exigindo o operador! =. stackoverflow.com/questions/11312448/…
steviekm3
3

Na minha opinião, você ainda não está tratando do mesmo problema que as std::tuplesoluções - ou seja, você tem que saber quantos e o nome de cada variável de membro, você está duplicando isso duas vezes na função. Você pode optar por privateherança.

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

Essa abordagem é um pouco mais complicada para começar, mas você está apenas mantendo as variáveis ​​e nomes em um lugar, em vez de em todos os lugares para cada operador que deseja sobrecarregar.

Cachorro
fonte
3
Então, eu estaria usando acessadores nomeados para as variáveis ​​como T& one_member(){ return std::get<0>(*this); } etc? Mas isso não precisaria de mim para fornecer tal método para cada "membro" que tenho, incluindo sobrecargas para versões const e não const?
Xeo
@Xeo Não vejo acessores nomeados exigindo mais trabalho do que a criação de variáveis ​​reais. De qualquer maneira, você teria que ter um nome separado para cada variável. Suponho que haveria duplicação para const / não const. No entanto, você pode modelar todo esse trabalho.
Lee Louviere
1

Se você planeja usar mais de uma sobrecarga de operador ou mais métodos de tupla, recomendo tornar a tupla um membro da classe ou derivar dela. Caso contrário, o que você está fazendo é muito mais trabalhoso. Ao decidir entre os dois, uma pergunta importante a ser respondida é: Você quer que sua classe seja uma tupla? Se não, eu recomendaria conter uma tupla e limitar a interface usando delegação.

Você pode criar acessores para "renomear" os membros da tupla.

Lee Louviere
fonte
Eu li a pergunta do OP no sentido de "está implementando minha classe ' operator<usando std::tierazoável?" Não entendo como essa resposta se relaciona a essa pergunta.
ildjarn
@ildjarn Há alguns comentários que não postei aqui. Compilei tudo para que seja lido melhor.
Lee Louviere