Implementar operadores de comparación a través de 'tupla' y 'empate', ¿una buena idea?

98

(Nota: tupley tiese puede tomar de Boost o C ++ 11.)
Cuando escribo estructuras pequeñas con solo dos elementos, a veces tiendo a elegir a std::pair, ya que todas las cosas importantes ya están hechas para ese tipo de datos, como operator<para el orden estricto-débil .
Sin embargo, las desventajas son los nombres de variables prácticamente inútiles. Incluso si yo mismo creé eso typedef, no recordaré 2 días después qué firsty qué secondfue exactamente, especialmente si ambos son del mismo tipo. Esto se vuelve aún peor para más de dos miembros, ya que anidar pairs apesta.
La otra opción para eso es untuple, ya sea de Boost o C ++ 11, pero eso en realidad no se ve más agradable y claro. Así que vuelvo a escribir las estructuras yo mismo, incluidos los operadores de comparación necesarios.
Dado que especialmente operator<puede ser bastante engorroso, pensé en eludir todo este lío simplemente confiando en las operaciones definidas para tuple:

Ejemplo de operator<, por ejemplo, para el orden estricto-débil:

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

( tieHace que una tuplede T&las referencias de los argumentos pasados.)


Editar : La sugerencia de @DeadMG de heredar de tupleforma privada no es mala, pero tiene algunos inconvenientes:

  • Si los operadores son independientes (posiblemente amigos), necesito heredar públicamente
  • Con el casting, mis funciones / operadores ( operator=específicamente) se pueden omitir fácilmente
  • Con la tiesolución, puedo dejar fuera a ciertos miembros si no son importantes para el pedido.

¿Hay algún inconveniente en esta implementación que deba considerar?

Xeo
fuente
1
Me parece perfectamente razonable ...
ildjarn
1
Esa es una idea muy inteligente, incluso si no funciona. Voy a tener que investigar esto.
templatetypedef
Esto parece bastante razonable. El único error en el que puedo pensar ahora es que tieno se puede aplicar a miembros de campo de bits.
Ise Wisteria
4
¡Me gusta esta idea! Si las tie(...)llamadas se van a duplicar en varios operadores (=, ==, <, etc.), puede escribir un método en línea privado make_tuple(...)para encapsularlo y luego llamarlo desde los otros lugares, como en return lhs.make_tuple() < rhs.make_tuple();(aunque el tipo de retorno de ¡Ese método podría ser divertido de declarar!)
aldo
13
@aldo: ¡C ++ 14 al rescate! auto tied() const{ return std::tie(the, members, here); }
Xeo

Respuestas:

60

Sin duda, esto hará que sea más fácil escribir un operador correcto que hacerlo tú mismo. Yo diría que solo considere un enfoque diferente si la creación de perfiles muestra que la operación de comparación es una parte de su aplicación que requiere mucho tiempo. De lo contrario, la facilidad de mantenimiento debería superar cualquier posible problema de rendimiento.

Marca B
fuente
17
No puedo imaginar un caso en tuple<>el operator<que sea más lento que uno escrito a mano.
ildjarn
51
Una vez tuve exactamente la misma idea e hice algunos experimentos. Me sorprendió positivamente ver que el compilador alineó y optimizó todo lo que tiene que ver con tuplas y referencias, emitiendo ensamblado casi idéntico al código escrito a mano.
JohannesD
7
@JohannesD: Puedo apoyar ese testimonio, hice lo mismo una vez
sehe
¿Garantiza esto un pedido estrictamente débil ? ¿Cómo?
CinCout
5

Me he encontrado con este mismo problema y mi solución usa plantillas variadic de c ++ 11. Aquí viene el código:

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

Y el .cpp para el caso base sin argumentos:

bool lexiLessthan()
{
  return false;
}

Ahora tu ejemplo se convierte en:

return lexiLessthan(
    lhs.one_member, rhs.one_member, 
    lhs.another, rhs.another, 
    lhs.yet_more, rhs.yet_more
);
user2369060
fuente
Puse una solución similar aquí pero no requiero el operador! =. stackoverflow.com/questions/11312448/…
steviekm3
3

En mi opinión, todavía no está abordando el mismo problema que std::tupleresuelve, es decir, debe saber cuántos y el nombre de cada variable miembro, lo está duplicando dos veces en la función. Podrías optar por la privateherencia.

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

Este enfoque es un poco más complicado para empezar, pero solo mantiene las variables y los nombres en un lugar, en lugar de en cada lugar para cada operador que desea sobrecargar.

Perrito
fuente
3
Entonces, ¿estaría usando accesores con nombre para las variables como, T& one_member(){ return std::get<0>(*this); }etc.? Pero, ¿no sería necesario que proporcione un método de este tipo para cada "miembro" que tengo, incluidas las sobrecargas para la versión const y no const?
Xeo
@Xeo No veo que los descriptores de acceso con nombre requieran más trabajo que la creación de variables reales. De cualquier manera, tendría que tener un nombre diferente para cada variable. Supongo que habrá duplicación para const / non-const. Sin embargo, puedes modelar todo este trabajo.
Lee Louviere
1

Si planea usar más de una sobrecarga de operador, o más métodos de tuple, le recomiendo hacer que tuple sea un miembro de la clase o derivar de tuple. De lo contrario, lo que estás haciendo es mucho más trabajo. Al decidir entre los dos, una pregunta importante a responder es: ¿Quieres que tu clase sea una tupla? Si no, recomendaría contener una tupla y limitar la interfaz mediante la delegación.

Puede crear descriptores de acceso para "cambiar el nombre" de los miembros de la tupla.

Lee Louviere
fuente
Leí la pregunta del OP en el sentido de "¿está implementando mi clase" operator<usando lo std::tierazonable? " No entiendo cómo esta respuesta se relaciona con esa pregunta.
ildjarn
@ildjarn Hay algunos comentarios que no publiqué aquí. Lo he compilado todo para que se lea mejor.
Lee Louviere