¿Por qué se concibieron los conceptos (programación genérica) cuando ya teníamos clases e interfaces?

8

También en stackoverflow.com :

Entiendo que los conceptos STL tenían que existir, y que sería una tontería llamarlos "clases" o "interfaces" cuando en realidad solo son conceptos documentados (humanos) y no se pueden traducir al código C ++ en ese momento, pero cuando se les dio la oportunidad de extender el lenguaje para acomodar conceptos, ¿por qué no simplemente modificaron las capacidades de las clases y / o las interfaces introducidas?

¿No es un concepto muy similar a una interfaz (clase 100% abstracta sin datos)? Al mirarlo, me parece que las interfaces solo carecen de soporte para axiomas, pero tal vez los axiomas podrían introducirse en las interfaces de C ++ (considerando una adopción hipotética de interfaces en C ++ para hacerse cargo de los conceptos), ¿no podrían? Creo que incluso los conceptos automáticos podrían agregarse fácilmente a dicha interfaz C ++ (interfaz automática LessThanComparable, ¿alguien?).

¿No es un concept_map muy similar al patrón del adaptador? Si todos los métodos están en línea, el adaptador esencialmente no existe más allá del tiempo de compilación; el compilador simplemente reemplaza las llamadas a la interfaz con las versiones en línea, llamando al objeto de destino directamente durante el tiempo de ejecución.

He oído hablar de algo llamado Programación Estática Orientada a Objetos, que esencialmente significa reutilizar efectivamente los conceptos de orientación a objetos en la programación genérica, permitiendo así el uso de la mayor parte del poder de OOP sin incurrir en gastos generales de ejecución. ¿Por qué no se consideró más esta idea?

Espero que esto sea lo suficientemente claro. Puedo reescribir esto si crees que no; sólo házmelo saber.

Gui Prá
fuente

Respuestas:

13

Respuesta corta: estás mezclando conceptos de tiempo de compilación anterior y tiempo de compilación que tienen similitudes en su propósito. Las interfaces (clases abstractas y toda la implementación del paradigma de orientación a objetos) se informan en tiempo de compilación . Los conceptos son la misma idea pero en el contexto de la programación genérica que en C ++ ocurre ANTES del tiempo de compilación . Todavía no tenemos esa última característica.

Pero déjame explicarte desde el principio.


Respuesta larga:

De hecho, los conceptos son solo información de lenguaje y "facilitan al programador" algo que ya está presente en el lenguaje, que podría llamarse "escritura de pato".

Cuando pasa un tipo a una función de plantilla, esa es una función genérica a partir de la cual el compilador generará código real (en línea) cuando se llama, ese tipo debe tener algunas propiedades (¿rasgos?) Que se utilizarán en el código de plantilla. Así que es la idea de escribir pato, PERO todo se genera y se hace en tiempo de compilación .

¿Qué sucede cuando el tipo no tiene las propiedades requeridas?

Bueno, el compilador sabrá que hay un problema solo una vez que el código generado a partir de la plantilla se compila y falla. Eso significa que el error que se generará será un error dentro del código de la plantilla, que se mostrará al programador como su error. Además, el error tendrá toneladas de información debido a las metainformaciones proporcionadas en caso de generación de código de plantilla, para saber de qué instancia de la plantilla estamos hablando.

Varios problemas con eso: primero, la mayoría de las veces, el código de plantilla es código de biblioteca y la mayoría de los programadores son usuarios de código de biblioteca, no escritores de código de biblioteca. Eso significa que este tipo de error críptico es realmente difícil de entender cuando no comprende cómo se escribe la biblioteca (no solo el diseño, cómo se implementa realmente). El segundo problema es que incluso cuando el programador escribió el código de la plantilla, las razones de la falla pueden ser oscuras porque el compilador podrá decir que hay un problema demasiado tarde: cuando se está compilando el código generado. Si el problema es relativo a las propiedades de tipo , debería comprobarlo incluso antes de generar el código.

Eso es lo que permiten los Conceptos (y están diseñados para): permitir que el programador (código genérico) especifique las propiedades de los tipos que se pasan como parámetros de plantilla y luego permitir que el compilador proporcione errores explícitos en caso de que los tipos proporcionados no cumplan con los requisitos requisitos

Una vez que la verificación es exitosa, el código se generará a partir de la plantilla y luego se compilará, ciertamente con éxito.

Todas las verificaciones de conceptos se realizan exclusivamente antes del tiempo de compilación . Comprueba los tipos ellos mismos, no los tipos de objetos . No hay ningún objeto antes del tiempo de compilación.

Ahora, sobre "interfaces".

Cuando crea un tipo base abstracto o virtual, permite que el código lo use para manipular objetos de los tipos secundarios sin conocer sus implementaciones reales. Para hacer cumplir esto, el tipo base expone miembros que son virtuales y podrían estar (o tienen que estar) sobrecargados por los tipos hijos.

Eso significa que el compilador puede verificar en el momento de la compilación que todos los objetos pasados ​​a una función que requiere una referencia a la clase base deben ser 1. de uno de los tipos secundarios de la clase base, 2. ese tipo secundario debe tener implementaciones de funciones puras virtuales declaradas en clases base si las hay.

Entonces, en tiempo de compilación , el compilador verificará las interfaces de los tipos de objetos e informará si falta algo.

Es la misma idea que Conceptos, pero ocurre demasiado tarde , como se dice en la descripción del Concepto. Ocurre en tiempo de compilación. No estamos en código genérico (código de plantilla), estamos después de que se haya procesado y ya es demasiado tarde para verificar si los tipos cumplen con los requisitos genéricos, que no pueden ser expuestos por las clases base virtuales. De hecho, toda la implementación del paradigma de orientación a objetos en C ++ ni siquiera existe cuando se procesa el código de la plantilla. No hay objetos (todavía). Eso es

Las clases describen restricciones en los objetos que se utilizarán para verificar los requisitos para las funciones que manipulan esos objetos. Los conceptos describen restricciones sobre los tipos (incluidas las clases) que se utilizarán para verificar los requisitos de código genérico para generar código real a partir de esos tipos y la combinación de código genérico.

Entonces, nuevamente, es el mismo "control de cordura", pero en otra capa del lenguaje, eso es plantillas. Las plantillas son un lenguaje completo (turing completo) que permite meta-programación, tipos de programación incluso antes de que aparezcan en el código compilado. Es un poco como crear un script para el compilador. Digamos que puede escribirlo, las clases son solo valores manipulados por el script. Actualmente, no hay forma de verificar las restricciones en estos valores que no sea bloquear el script de una manera no obvia. Los conceptos son solo eso: proporcionar tipeo en estos valores (que en el código generado son tipos). No estoy seguro de que esté claro ...

Otra diferencia realmente importante entre las clases base virtuales y los Conceptos es que la primera fuerza una fuerte relación entre los tipos, haciéndolos "atados por la sangre". Mientras que la metaprogramación de plantillas permite "escribir pato" que los Conceptos simplemente permiten aclarar los requisitos.

Klaim
fuente
4

Declarar una clase es " objetos de programación ".
Declarar un concepto es " programar clases ".

Por supuesto, como siempre es la programación , se pueden ver ciertas analogías, pero las dos cosas pertenecen a una fase diferente del proceso de abstracción. Básicamente, una "clase" (y todo lo que la rodea, como "interfaces") le dice al compilador cómo estructurar los objetos que la máquina ejecutora instanciará en tiempo de ejecución. Un "concepto" tiene la intención de decirle al compilador cómo debe estructurarse una "clase" para ser "compilable" en un contexto dado.

Por supuesto, es teóricamente posible reiterar estos pasos una y otra vez teniendo

  • objetos
  • tipos de objetos ( clases )
  • tipos de (tipos de objetos) ( conceptos )
  • tipos de (tipos de (tipos de objetos)) ( ??? )
  • .....

En este momento, los "conceptos" han sido eliminados por las especificaciones C ++ 0x (ya que todavía requieren algo de trabajo, y se retuvo ya no era el caso para retrasar) La idea del concepto n No sé, ahora mismo, si puede ser siempre útil

Emilio Garavaglia
fuente
Eso tiene mucho sentido conceptual; Estoy tratando de asimilarlo y pensar si esto responde la pregunta. Muchas gracias.
Gui Prá
3

La respuesta simple a prácticamente todas sus preguntas es: "Porque los compiladores de C ++ apestan". Seriamente. Se basan en la tecnología de la Unidad de Traducción de C, que efectivamente prohíbe muchas cosas útiles, y las implementaciones de plantillas existentes son horriblemente lentas. Los conceptos no se cortaron por ninguna razón conceptual: se cortaron porque no había una implementación confiable, ConceptGCC fue extremadamente lento y la especificación de conceptos tomó un tiempo absurdamente largo. Herb Sutter declaró que se necesitaba más espacio para especificar los conceptos utilizados en la biblioteca estándar que para especificar la totalidad de las plantillas.

Podría decirse que entre SFINAE decltypey static_assert, en su mayoría, son implementables como lo es ahora de todos modos.

DeadMG
fuente
2

Según tengo entendido, las Interfaces y los Conceptos tienen propósitos similares en diferentes partes del lenguaje C ++.

Como se menciona en la respuesta a la pregunta original: la implementación de una interfaz es decidida por el implementador de una clase en tiempo de diseño. Una vez que se ha publicado una clase, puede admitir solo aquellas interfaces de las que se derivó en tiempo de diseño.

Dos interfaces distintas con exactamente las mismas funciones miembro y semántica (es decir, el mismo concepto) seguirán siendo dos interfaces distintas. Si desea admitir la semántica de ambas interfaces, es posible que deba implementar el soporte dos veces.

Ese es el problema que la Programación Genérica intenta solucionar. En la programación genérica de C ++, el tipo pasado a una plantilla simplemente necesita admitir la interfaz (sin mayúscula, en el sentido de la "interfaz de programación" de un tipo) que utiliza la plantilla. Las dos interfaces distintas con las mismas funciones miembro coincidirán, y deberá escribir el código solo una vez. Además, funcionará cualquier tipo (incluso sin interfaces explícitas) que admitan la misma interfaz.

Esto lleva a un segundo problema: ¿qué sucede si tiene dos tipos con interfaces superpuestas pero con semántica diferente ? La programación genérica no podrá notar la diferencia y todo se compilará bien, pero el resultado en tiempo de ejecución será sorprendente y probablemente incorrecto.

Ahí es donde entran los conceptos. Si simplifica demasiado, puede considerar un concepto como la versión genérica (plantilla) de una interfaz. Debe implementarse solo una vez para aplicarse a una gran cantidad de tipos potenciales que pueden "derivarse" del Concepto. Un concepto es una interfaz semántica predeterminada de un tipo (clase) que no se limita únicamente a ese tipo. Es diferente a una interfaz en que dos tipos muy diferentes (para el compilador genérico) todavía pueden tener el mismo concepto, sin tener que recurrir a interfaces de clase base limitantes o interfaces de clase base que no tengan una semántica real visible para el compilador de sus propio, pero solo se utilizan para la distinción de tipo.

Joris Timmermans
fuente
Espera ... Dijiste que los conceptos se implementan una vez y se aplican a una gran cantidad de tipos potenciales que pueden "derivarse" de él. Eso significa que todos los tipos potenciales tienen que derivar de él. Si dos conceptos con el mismo contenido están presentes en dos bibliotecas diferentes, sin mapeo automático, tiene el mismo problema que las Interfaces. Al mismo tiempo, una interfaz bien podría presentar mapeo automático. Los problemas me parecen iguales.
Gui Prá
1
@ n2liquid: los conceptos son una combinación diferente de beneficios y desventajas entre las interfaces y la programación genérica pura. No son una mejora estricta. Los conceptos no evitan la parte "predeterminada" de las interfaces. Los conceptos evitan la situación en la que los tipos de clase admiten la misma semántica pero no pueden derivar de la misma interfaz (por ejemplo, donde la interfaz se define para el subtipo doble, mientras que la versión genérica se aplica a todos los tipos numéricos).
Joris Timmermans