Se supone que los tipos con plantilla deben seguir un "concepto" (Input Iterator, Forward Iterator, etc ...) donde los detalles reales del concepto se definen completamente por la implementación de la función / clase de plantilla, y no por la clase del tipo usado con la plantilla, que es un poco anti-uso de OOP.
Creo que malinterpretas el uso previsto de los conceptos por parte de las plantillas. Forward Iterator, por ejemplo, es un concepto muy bien definido. Para encontrar las expresiones que deben ser válidas para que una clase sea un iterador directo, y su semántica, incluida la complejidad computacional, consulte el estándar o http://www.sgi.com/tech/stl/ForwardIterator.html (tiene que seguir los enlaces a Entrada, Salida e Iterador trivial para verlo todo).
Ese documento es una interfaz perfectamente buena, y "los detalles reales del concepto" se definen allí mismo. No están definidos por las implementaciones de Forward Iterators, y tampoco están definidos por los algoritmos que usan Forward Iterators.
Las diferencias en cómo se manejan las interfaces entre STL y Java son triples:
1) STL define expresiones válidas utilizando el objeto, mientras que Java define métodos que deben ser invocables en el objeto. Por supuesto, una expresión válida podría ser una llamada a método (función miembro), pero no tiene que ser así.
2) Las interfaces Java son objetos de tiempo de ejecución, mientras que los conceptos de STL no son visibles en tiempo de ejecución incluso con RTTI.
3) Si no puede hacer válidas las expresiones válidas requeridas para un concepto STL, obtendrá un error de compilación no especificado cuando crea una instancia de alguna plantilla con el tipo. Si no puede implementar un método requerido de una interfaz Java, recibirá un error de compilación específico que lo indica.
Esta tercera parte es si desea una especie de "tipeo de pato" (tiempo de compilación): las interfaces pueden ser implícitas. En Java, las interfaces son algo explícitas: una clase "es" Iterable si y solo si dice que implementa Iterable. El compilador puede verificar que las firmas de sus métodos estén todas presentes y sean correctas, pero la semántica aún está implícita (es decir, están documentadas o no, pero solo más código (pruebas unitarias) pueden decirle si la implementación es correcta).
En C ++, como en Python, tanto la semántica como la sintaxis están implícitas, aunque en C ++ (y en Python si obtienes el preprocesador de tipo fuerte) obtienes ayuda del compilador. Si un programador requiere una declaración explícita de interfaces similar a Java por parte de la clase implementadora, entonces el enfoque estándar es usar rasgos de tipo (y la herencia múltiple puede evitar que esto sea demasiado detallado). Lo que falta, en comparación con Java, es una plantilla única que puedo instanciar con mi tipo, y que compilará si y solo si todas las expresiones requeridas son válidas para mi tipo. Esto me diría si he implementado todos los bits requeridos, "antes de usarlo". Eso es una conveniencia, pero no es el núcleo de OOP (y todavía no prueba la semántica,
STL puede o no ser lo suficientemente OO para su gusto, pero ciertamente separa la interfaz limpiamente de la implementación. Carece de la capacidad de Java para hacer reflexiones sobre las interfaces, y reporta infracciones de los requisitos de la interfaz de manera diferente.
puede decir la función ... espera un iterador directo solo mirando su definición, donde necesitaría mirar la implementación o la documentación para ...
Personalmente, creo que los tipos implícitos son una fortaleza, cuando se usan adecuadamente. El algoritmo dice lo que hace con sus parámetros de plantilla, y el implementador se asegura de que esas cosas funcionen: es exactamente el denominador común de lo que deberían hacer las "interfaces". Además, con STL, es poco probable que utilice, por ejemplo, std::copy
basándose en encontrar su declaración de reenvío en un archivo de encabezado. Los programadores deberían determinar qué toma una función en función de su documentación, no solo de la firma de la función. Esto es cierto en C ++, Python o Java. Existen limitaciones sobre lo que se puede lograr con la escritura en cualquier idioma, y tratar de usar la escritura para hacer algo que no hace (verificar semántica) sería un error.
Dicho esto, los algoritmos STL generalmente nombran sus parámetros de plantilla de una manera que deja en claro qué concepto se requiere. Sin embargo, esto es para proporcionar información adicional útil en la primera línea de la documentación, no para hacer declaraciones futuras más informativas. Hay más cosas que necesita saber que se pueden encapsular en los tipos de parámetros, por lo que debe leer los documentos. (Por ejemplo, en algoritmos que toman un rango de entrada y un iterador de salida, lo más probable es que el iterador de salida necesite suficiente "espacio" para un cierto número de salidas en función del tamaño del rango de entrada y tal vez los valores en él. Intente escribirlo con firmeza. )
Aquí está Bjarne en las interfaces declaradas explícitamente: http://www.artima.com/cppsource/cpp0xP.html
En genéricos, un argumento debe ser de una clase derivada de una interfaz (el equivalente de C ++ a la interfaz es una clase abstracta) especificada en la definición del genérico. Eso significa que todos los tipos de argumentos genéricos deben caber en una jerarquía. Eso impone restricciones innecesarias en los diseños requiere una previsión irrazonable por parte de los desarrolladores. Por ejemplo, si escribe un genérico y yo defino una clase, las personas no pueden usar mi clase como argumento para su genérico a menos que yo conozca la interfaz que especificó y haya derivado mi clase de ella. Eso es rígido
Mirándolo al revés, con pato escribiendo puede implementar una interfaz sin saber que la interfaz existe. O alguien puede escribir una interfaz deliberadamente para que su clase la implemente, después de consultar sus documentos para ver que no piden nada que usted no haya hecho. Eso es flexible