¿Por qué el uso de tuplas en C ++ no es más común?

124

¿Por qué nadie parece usar tuplas en C ++, ya sea Boost Tuple Library o la biblioteca estándar para TR1? He leído mucho código C ++, y muy rara vez veo el uso de tuplas, pero a menudo veo muchos lugares donde las tuplas resolverían muchos problemas (generalmente devolviendo valores múltiples de las funciones).

Las tuplas te permiten hacer todo tipo de cosas geniales como esta:

tie(a,b) = make_tuple(b,a); //swap a and b

Eso es ciertamente mejor que esto:

temp=a;
a=b;
b=temp;

Por supuesto, siempre puedes hacer esto:

swap(a,b);

Pero, ¿qué pasa si quieres rotar tres valores? Puedes hacer esto con tuplas:

tie(a,b,c) = make_tuple(b,c,a);

Las tuplas también hacen que sea mucho más fácil devolver múltiples variables de una función, lo cual es probablemente un caso mucho más común que intercambiar valores. Usar referencias a valores de retorno ciertamente no es muy elegante.

¿Hay grandes inconvenientes para las tuplas en las que no estoy pensando? Si no, ¿por qué rara vez se usan? ¿Son más lentos? ¿O es solo que la gente no está acostumbrada a ellos? ¿Es una buena idea usar tuplas?

Zifre
fuente
17
+1 para truco inteligente de intercambio de tuplas :)
kizzx2
10
a = a ^ b; b = a ^ b; a = a ^ b;
Gerardo Marset
3
Las tuplas de la OMI son convenientes en lenguajes de mecanografía débiles o idiomas en los que son estructuras nativas. Por ejemplo, en Python o PHP simplemente hacen la vida más fácil, mientras que en C ++ hay demasiada tipificación (para construirla desde la plantilla) y muy pocos beneficios.
doc
44
Un comentario al OP: creo que la respuesta aceptada actual ya está obsoleta hasta el punto de estar objetivamente equivocada. Es posible que desee reconsiderar la elección de la respuesta aceptada.
ulidtko
8
@GerardoMarset ¿Hablas en serio?
thesaint

Respuestas:

43

Porque aún no es estándar. Cualquier cosa no estándar tiene un obstáculo mucho mayor. Las piezas de Boost se han vuelto populares porque los programadores clamaban por ellas. (hash_map salta a la mente). Pero aunque la tupla es útil, no es una victoria tan abrumadora y clara que la gente se moleste con ella.

Alan De Smet
fuente
1
La gente parece usar otras partes de Boost como locos. Aunque ciertamente los mapas hash son mucho más útiles en general que las tuplas.
Zifre
No sé los detalles de lo que estás viendo, pero supongo que las partes que las personas usan como locos son características que realmente querían. Por lo tanto (nuevamente, adivinando) la popularidad del mapa hash, el puntero contado y similares. La tupla es útil, pero no es algo que salte para llenar un agujero. La recompensa no es obvia. ¿Con qué frecuencia necesita rotar exactamente N objetos? (A diferencia de la necesidad de rotar vectores arbitrariamente largos). Y las personas están acostumbradas a pasar valores de retorno por referencia, o devolver clases o estructuras pequeñas.
Alan De Smet
20
Actualmente es parte del estándar C ++ 11 ahora: en.cppreference.com/w/cpp/utility/tuple
Roman Susi
124

Una respuesta cínica es que muchas personas programan en C ++, pero no entienden y / o usan la funcionalidad de nivel superior. A veces es porque no están permitidos, pero muchos simplemente no lo intentan (ni siquiera entienden).

Como un ejemplo sin impulso: ¿cuántas personas usan la funcionalidad que se encuentra en <algorithm>?

En otras palabras, muchos programadores de C ++ son simplemente programadores de C que usan compiladores de C ++, y quizás std::vectory std::list. Esa es una razón por la cual el uso de boost::tupleno es más común.

Trey Jackson
fuente
18
-1 de mí porque los programadores de C ++ no son tan tontos como esta respuesta los hace sonar.
user541686
55
@Mehrdad Habiendo examinado una gran cantidad de código C ++, tanto comercial como no, leyendo toneladas de material C ++, creo que es bastante seguro decir que una gran parte de los desarrolladores de "C ++" son solo desarrolladores de C que no pudieron obtener un puro C compilador. Las plantillas, por ejemplo, faltan casi por completo en la mayoría de los materiales (algo que he aprendido a amar mucho). Extraños hacks de macros son comunes y el espacio de nombres está muy infrautilizado.
Más claro el
55
Respuesta sin sentido. Si uno los necesita, los alcanzará. No son necesarios; entonces no se usan. Decir que no se usan porque no se entienden fácilmente es malo.
Michael Chourdakis
9
@Michael Comentario sin sentido. No se necesita nada en la programación una vez que se tiene un subconjunto completo de lenguaje de Turing. La falta de uso ciertamente no implica que todos comprendan las construcciones de C ++ de nivel superior y elijan no usarlas.
Trey Jackson
44
Tbh NUNCA tuve la necesidad de std :: tuple fuera de la metaprogramación de plantilla variable. No había ningún punto en la vida donde me sentaba con una cara triste pensando "Si tan solo tuviera esas tuplas". De hecho, cuando miro las tuplas pienso "¿Para qué demonios alguien las necesitaría? (No me consideraría cuerda)". La gente fuera de la metaprogramación parece usarlos como "Estructura anónima", que es tan fea e indica una fuerte ausencia de calidad de código y capacidad de mantenimiento en sus cabezas.
thesaint
23

La sintaxis de tupla de C ++ puede ser bastante más detallada de lo que a la mayoría de la gente le gustaría.

Considerar:

typedef boost::tuple<MyClass1,MyClass2,MyClass3> MyTuple;

Entonces, si desea hacer un uso extensivo de las tuplas, puede obtener tuple typedefs en todas partes u obtener nombres de tipo molestamente largos en todas partes. Me gustan las tuplas Los uso cuando sea necesario. Pero generalmente se limita a un par de situaciones, como un índice de elementos N o cuando se usan mapas múltiples para vincular los pares de iteradores de rango. Y generalmente tiene un alcance muy limitado.

Todo es muy feo y hacky en comparación con algo como Haskell o Python. Cuando C ++ 0x llegue aquí y obtengamos las tuplas de palabras clave 'auto' comenzarán a parecer mucho más atractivas.

La utilidad de las tuplas es inversamente proporcional al número de pulsaciones de teclas necesarias para declararlas, empaquetarlas y desempaquetarlas.

usuario21714
fuente
La mayoría de la gente hará "uso de impulso de espacio de nombres"; y no tiene que escribir boost ::. No creo que escribir tupla sea un gran problema. Dicho esto, creo que tienes un punto. auto podría hacer que muchas más personas comiencen a usar tuplas.
Zifre
2
@Zifre: el problema es que no debe "usar el espacio de nombres X" dentro de un archivo de encabezado porque fuerza la contaminación del espacio de nombres y subvierte los espacios de nombres.
Sr. Fooz
1
Ah, sí, me olvido de los encabezados. Pero dentro del código del programa, no necesita preocuparse por eso. Y una vez que tenemos C ++ 0x, podemos usar auto, lo que debería eliminar una gran cantidad de tipeo.
Zifre
19
¿Se trata sólo de mí? No creo que se haya referido a guardar los 7 caracteres de "boost ::", sino a los otros 33 caracteres . Eso es una gran cantidad de tipeo de nombres de clase, especialmente si esos también tienen un alcance de espacio de nombres. Tome boost :: tuple <std :: string, std :: set <std :: string>, std :: vector <My :: Scoped :: LongishTypeName>> como un ejemplo ridículo.
Ogre Psalm33
10

Para mí, es un hábito, sin lugar a dudas: las tuplas no resuelven ningún problema nuevo para mí, solo algunas que ya puedo manejar bien. El intercambio de valores todavía se siente más fácil a la antigua usanza y, lo que es más importante, realmente no pienso en cómo intercambiar "mejor". Es lo suficientemente bueno como está.

Personalmente, no creo que las tuplas sean una gran solución para devolver múltiples valores, parece un trabajo para structs.

ojrac
fuente
44
"Realmente no pienso en cómo intercambiar" mejor "". Cuando escribo código, escribo errores. La reducción de la complejidad del código reduce la cantidad de errores que escribo. Odio crear los mismos errores una y otra vez. Sí, sí pienso en cómo <strike> intercambiar </> código mejor . Menos partes móviles (LOC, variables temporales, identificadores para escribir mal), código más legible; Buen código.
sehe
De acuerdo. Una clase envuelta en puntero automático o un puntero inteligente es guardar tipografía. He usado tuplas una vez, pero luego reescribí el código usando clases. retValue.state es más claro que retValue.get <0> ().
Valentin Heinitz
1
@sehe: Escribir mi código mejor y más legible también es mi objetivo. Agregar más tipos de sintaxis tiene un costo, y no creo que "un mejor intercambio" justifique la sobrecarga mental de pensar en aún más tipos de sintaxis para cada línea de código que lea.
ojrac
8

Pero, ¿qué pasa si quieres rotar tres valores?

swap(a,b);
swap(b,c);  // I knew those permutation theory lectures would come in handy.

Bien, entonces con 4 valores de etc., eventualmente la n-tupla se convierte en menos código que n-1 intercambios. Y con el intercambio predeterminado, esto hace 6 asignaciones en lugar de las 4 que tendría si implementara una plantilla de tres ciclos usted mismo, aunque espero que el compilador lo resuelva para tipos simples.

Puede crear escenarios en los que los intercambios sean difíciles de manejar o inapropiados, por ejemplo:

tie(a,b,c) = make_tuple(b*c,a*c,a*b);

Es un poco incómodo desempacar.

Sin embargo, el punto es que hay formas conocidas de tratar las situaciones más comunes para las que las tuplas son buenas y, por lo tanto, no hay una gran urgencia para tomarlas. Por lo menos, no estoy seguro de que:

tie(a,b,c) = make_tuple(b,c,a);

no hace 6 copias, lo que lo hace completamente inadecuado para algunos tipos (las colecciones son las más obvias). Siéntase libre de persuadirme de que las tuplas son una buena idea para los tipos "grandes", diciendo que esto no es así :-)

Para devolver múltiples valores, las tuplas son perfectas si los valores son de tipos incompatibles, pero a algunas personas no les gustan si es posible que la persona que llama los ponga en el orden incorrecto. A algunas personas no les gustan los valores de retorno múltiples, y no quieren alentar su uso haciéndolos más fáciles. Algunas personas simplemente prefieren estructuras con nombre para parámetros de entrada y salida, y probablemente no podrían ser persuadidos con un bate de béisbol para usar tuplas. No tiene en cuenta el sabor.

Steve Jessop
fuente
1
Definitivamente no querrás intercambiar vectores con tuplas. Creo que intercambiar tres elementos es ciertamente más claro con las tuplas que con dos intercambios. En cuanto a los valores de retorno múltiples, los parámetros de salida son malos, las estructuras son de tipeo adicional y definitivamente hay casos en los que se necesitan valores de retorno múltiples.
Zifre
sé por qué usas tuplas (y sé por qué las usaría si surgiera la ocasión, aunque creo que nunca lo ha hecho). Supongo por qué otras personas no los usan, incluso si son conscientes de ellos. por ejemplo, porque no están de acuerdo con el "fuera params son malos" ...
Steve Jessop
¿Sabes si podemos reemplazar "tie (a, b, c) = make_tuple (b, c, a);" por "empate (a, b, c) = empate (b, c, a);" ?
Rexxar
2
Un empate (bueno, un nivel técnico) es una tupla hecha con referencias no constantes. No puedo encontrar documentación de impulso que diga qué garantiza operator = y el constructor de copia para tie / tuple cuando algunas de las referencias involucradas tienen el mismo rendond. Pero eso es lo que necesitas saber. Una implementación ingenua del operador = claramente podría ir muy mal ...
Steve Jessop
1
@Steve: Y dado que los vínculos tienen que ver con la prevención de copias (deberían funcionar para tipos no copiables; tenga en cuenta que el LHS podría ser algo completamente diferente) de hecho, todo debería salir muy mal (piense en objetos de clase que no sean POD). Solo imagine cómo escribiría la misma lógica sin usar temporales.
sehe
7

Como mucha gente señaló, las tuplas no son tan útiles como otras características.

  1. Los trucos de intercambio y rotación son solo trucos. Son completamente confusos para aquellos que no los han visto antes, y dado que son casi todos, estos trucos son simplemente una mala práctica de ingeniería de software.

  2. Devolver múltiples valores usando tuplas es mucho menos autodocumentado que las alternativas: devolver tipos con nombre o usar referencias con nombre. Sin esta autodocumentación, es fácil confundir el orden de los valores devueltos, si son mutuamente convertibles y no son más sabios.

igorlord
fuente
6

No todos pueden usar boost, y TR1 aún no está ampliamente disponible.

Brian Neal
fuente
3
Mucha gente usa Boost. Esas personas podrían estar usando tuplas también.
Zifre
3
Usted preguntó por qué la gente no los usa, y yo le respondí.
Brian Neal
2
Para el votante negativo: trabajo en un lugar donde es políticamente imposible usar boost, e incluso en esta fecha, la cadena de herramientas del compilador que utilizamos (para un sistema integrado) no tiene soporte TR1 / C ++ 11.
Brian Neal
5

Cuando se usa C ++ en sistemas integrados, la extracción de las bibliotecas Boost se vuelve compleja. Se acoplan entre sí, por lo que el tamaño de la biblioteca crece. Devuelve estructuras de datos o usa paso de parámetros en lugar de tuplas. Al devolver tuplas en Python, la estructura de datos está en el orden y el tipo de los valores devueltos simplemente no es explícito.

DanM
fuente
5

Raramente los ve porque el código bien diseñado generalmente no los necesita; no hay muchos casos en la naturaleza en los que el uso de una estructura anónima sea superior al uso de uno con nombre. Como todo lo que una tupla realmente representa es una estructura anónima, la mayoría de los codificadores en la mayoría de las situaciones simplemente van con lo real.

Digamos que tenemos una función "f" donde un retorno de tupla podría tener sentido. Como regla general, tales funciones suelen ser tan complicadas que pueden fallar.

Si "f" PUEDE fallar, necesita un retorno de estado; después de todo, no desea que las personas que llaman tengan que inspeccionar cada parámetro para detectar la falla. "f" probablemente encaja en el patrón:

struct ReturnInts ( int y,z; }
bool f(int x, ReturnInts& vals);

int x = 0;
ReturnInts vals;
if(!f(x, vals)) {
    ..report error..
    ..error handling/return...
}

Eso no es bonito, pero mira lo fea que es la alternativa. Tenga en cuenta que todavía necesito un valor de estado, pero el código no es más legible y no más corto. Probablemente también sea más lento, ya que incurro en el costo de 1 copia con la tupla.

std::tuple<int, int, bool> f(int x);
int x = 0;
std::tuple<int, int, bool> result = f(x); // or "auto result = f(x)"
if(!result.get<2>()) {
    ... report error, error handling ...
}

Otro inconveniente importante está oculto aquí: con "ReturnInts" puedo agregar el retorno de "f" modificando "ReturnInts" SIN ALTERAR LA INTERFAZ de "f". La solución de tupla no ofrece esa característica crítica, lo que la convierte en la respuesta inferior para cualquier código de biblioteca.

Zack Yezek
fuente
1
Las excepciones hacen que esa interfaz sea mucho más limpia.
David Stone
Para ser justos (y extremadamente tarde para la fiesta), podría facilitar la legibilidad configurando using std::tuple;y simplemente usar tupleen el código.
Alrek
2
El uso tuplehace que el código sea menos legible, no más. La mayoría de los códigos en estos días tienen una gran cantidad de símbolos, ya std::tupleque al verlos queda a la vista exactamente lo que es.
Tom Swirly
3

Ciertamente, las tuplas pueden ser útiles, pero como se mencionó, hay un poco de sobrecarga y un obstáculo o dos que debes superar antes de que realmente puedas usarlas.

Si su programa encuentra constantemente lugares donde necesita devolver múltiples valores o intercambiar varios valores, puede valer la pena ir a la ruta de las tuplas, pero de lo contrario a veces es más fácil hacer las cosas de la manera clásica.

En términos generales, no todo el mundo ya tiene instalado Boost, y ciertamente no pasaría por la molestia de descargarlo y configurar mis directorios de inclusión para que trabajen con él solo por sus instalaciones de tupla. Creo que descubrirá que las personas que ya usan Boost tienen más probabilidades de encontrar usos de tuplas en sus programas que los usuarios que no son de Boost, y los migrantes de otros idiomas (Python viene a la mente) tienen más probabilidades de estar molestos por la falta de tuplas. en C ++ que explorar métodos para agregar soporte de tuplas.

Zac
fuente
1

Como un almacén de datos std::tupletiene las peores características tanto de a structcomo de una matriz; todo el acceso se basa en la enésima posición, pero no se puede iterar mediante tupleun forbucle.

Entonces, si los elementos en el tupleson conceptualmente una matriz, usaré una matriz y si los elementos no son conceptualmente una matriz, una estructura (que tiene elementos nombrados) es más fácil de mantener. ( a.lastnameEs más explicativo que std::get<1>(a)).

Esto deja la transformación mencionada por el OP como el único caso de uso viable para las tuplas.

doron
fuente
0

Tengo la sensación de que muchos usan Boost.Any y Boost.Variant (con algo de ingeniería) en lugar de Boost.Tuple.

Dirkgently
fuente
¿Por qué cambiaría una escritura estática eficiente por algo así?
Zifre
Boost.Variant es completamente seguro para escribir.
user21714
1
Vaya, sí, es seguro, pero escribe en tiempo de ejecución.
Zifre
55
No veo cómo Tuple podría reemplazarse Any / Variant. No hacen lo mismo.
Mankarse
1
@Zifre No puedo hablar por el autor, pero creo que la implicación aquí es usarlos junto con otros tipos de contenedores.
Tim Seguine