type restricciones parameter new multiple method generic example constraint c# java c++ templates generics

restricciones - type parameter c#



¿En qué se diferencia una plantilla de una genérica? (6)

Hay una serie de cosas que los genéricos de Java pueden hacer que C # y C ++ no pueden (por ejemplo, hacer un parámetro de tipo acotado de una familia de genéricos como la class Foo<T extends Comparable<?>> )

No es del todo cierto para ese ejemplo:

template <typename Comparable> struct Foo { static bool compare(const Comparable &lhs, const Comparable &rhs) { return lhs == rhs; } };

Esta plantilla de clase creará una instancia exitosa de la función de compare solo si el parámetro de la plantilla es un tipo comparable a la igualdad. No se llama un "parámetro de tipo acotado", pero sirve para el mismo propósito.

Si en C ++ quiere tratar a Comparable como una interfaz explícita (es decir, una clase base) en lugar de un concepto de tipo pato, entonces puede static_assert(is_base_of<Comparable, T>::value, "objects not Comparable"); , o lo que sea.

Entiendo los aspectos de las plantillas en C ++ que son diferentes de los genéricos en Java y C #. C # es una reificación, Java usa borrado de tipo, C ++ usa tipografía duck, etc. Hay varias cosas que las plantillas de C ++ pueden hacer que Java y C # no pueden (por ejemplo, la especialización de plantillas). Pero hay una serie de cosas que los genéricos de Java pueden hacer que C # y C ++ no pueden (por ejemplo, hacer un parámetro de tipo acotado de una familia de genéricos como la class Foo<T extends Comparable<?>> ), y un número de cosas genéricas de C # puede hacer que Java y C ++ no puedan (p. ej., reflexión genérica en tiempo de ejecución). [EDITAR: Aparentemente los genéricos de Java son mucho más débiles de lo que pensaba. (Lo que está diciendo algo). En cualquier caso, a pesar de su ineptitud, todavía se consideran genéricos junto con los genéricos de C #.

Lo que no entiendo es lo que conceptualmente hace que las plantillas sean diferentes de los genéricos. ¿Qué partes de las plantillas de C ++ son cosas que no se pueden hacer en algo que no es una plantilla, pero que es genérico? Por ejemplo, si tuviera que implementar un lenguaje que admitiera plantillas, ¿qué tendría que haber en él? ¿Qué podría dejar de lado que sería necesario para que el lenguaje apoye los genéricos?

Mi conjetura es que las plantillas son un superconjunto de genéricos o son una forma de implementar genéricos, pero realmente no entiendo qué separa una verdadera plantilla de un verdadero genérico.


Este es un hilo antiguo, y mi representante es demasiado bajo para comentar sobre la respuesta aceptada , pero quería agregar:

Además de la especialización explícita, otra diferencia clave en las plantillas de C ++ y los genéricos de C # son los parámetros de plantilla que no son de tipo utilizados en C ++:

template<int bar> class Foo {}; Foo<1> a; Foo<2> b; a = b; //error, different types.

Los parámetros de plantilla que no son de tipo pueden ser de cualquier tipo entero, enumeración, así como los punteros que se pueden determinar en el momento de la compilación (variables de almacenamiento estático y punteros de función). En C ++ 20, también pueden ser de clase con ciertas restricciones.

Ni C # ni los genéricos de Java pueden hacer eso.

También puede especializarse explícitamente en parámetros que no sean de tipo.

Como nota al margen, el lenguaje de programación D usa el término "plantilla" como nomenclatura para la programación genérica, y sus características se sienten más alineadas en espíritu con C ++ que con C # / Java al menos.

No tengo conocimiento de las razones técnicas por las que los parámetros que no son de tipo se quedaron fuera de C #, pero como el lenguaje que uso más que otros en estos días, extraño la función de vez en cuando.


Hm ... si dice que entiende las plantillas de C ++ en profundidad y dice que no ve / siente la diferencia entre los genéricos y ellos, bueno, lo más probable es que tenga razón :)

Hay muchas diferencias que describirán cómo / por qué los genéricos son mejores que las plantillas, enumera toneladas de diferencias, etc., pero eso no es relevante para el núcleo de la idea.

La idea es permitir una mejor reutilización del código. Las plantillas / genéricos le proporcionan una forma de crear un tipo de definiciones de clase de orden superior que se abstraen sobre algunos de los tipos reales.

En este sentido, no hay diferencia entre ellos, y las únicas diferencias son aquellas impuestas por características y restricciones específicas del lenguaje subyacente y el tiempo de ejecución.

Se puede argumentar que los genéricos proporcionan algunas características adicionales (generalmente cuando se habla de la introspección dinámica del árbol de clases del objeto), pero muy pocas de ellas (si es que hay alguna) no se pueden implementar manualmente en las plantillas de C ++. Con un poco de esfuerzo, la mayoría de ellos pueden implementarse o emularse, por lo que no son buenos como distinción entre "genéricos apropiados" y "plantillas reales".

Otros argumentarán que el poder potencial de optimización que está disponible gracias al comportamiento de copiar y pegar de C ++ es la diferencia. Lo siento, no es cierto. Los JIT en Java y C # también pueden hacerlo, bueno, casi, pero hacerlo muy bien.

Sin embargo, hay una cosa que realmente podría hacer que los genéricos de Java / C # sean un verdadero subconjunto de las características de las plantillas de C ++. ¡Y hasta lo has mencionado!

Es la especialización de plantillas .

En C ++, cada especialización se comporta como una definición completamente diferente.

En C ++, la template<typename T> Foo especializó para T == int:

class Foo<int> { void hug_me(); int hugs_count() const; }

mientras que "la misma" plantilla especializada para T == MyNumericType puede parecer

class Foo<MyNumericType> { void hug_me(); MyNumericType get_value() const; void reset_value() const; }

FYI: eso es solo pseudocódigo, no compilará :)

Ni los genéricos de Java ni los de C # pueden hacer eso, porque su definición establece que todas las materializaciones de tipo genérico tendrán la misma "interfaz de usuario".

Más que eso, C ++ usa una regla SFINAE. Muchas definiciones de "colisiones teóricamente" pueden existir para una plantilla. Sin embargo, cuando se utiliza la plantilla, solo se utilizan los "realmente buenos".

Con clases similares al ejemplo anterior, si usas:

Foo<double> foood; foood.reset_value();

solo se usaría la segunda especialización, ya que la primera no se compilaría debido a que ... falta "reset_value".

Con los genéricos, no puedes hacer eso. Necesitaría crear una clase genérica que tenga todos los métodos posibles, y luego, en tiempo de ejecución, inspeccionaría dinámicamente los objetos internos y lanzaría algunas excepciones "no implementadas" o "no admitidas" para los métodos no disponibles. Eso es ... simplemente horrible. Tales cosas deberían ser posibles en tiempo de compilación.

El poder real, las implicaciones, los problemas y la complejidad general de la especialización de plantillas y SFINAE es lo que realmente diferencia los genéricos y las plantillas. Simplemente, los genéricos se definen de tal manera, que la especialización no es posible, por lo tanto, SFINAE no es posible, por lo tanto, todo el mecanismo es, paradójicamente, mucho más sencillo / simple.

Tanto más fácil / más simple de implementar en los componentes internos del compilador, como para que lo entiendan los cerebros no sabios.

Aunque estoy de acuerdo con los beneficios generales de los genéricos en Java / C #, realmente extraño las especializaciones, la flexibilidad de la interfaz y la regla SFINAE. Sin embargo, no sería justo si no mencionara una cosa importante relacionada con el diseño sano de OO: si la especialización de plantillas para el tipo xxx realmente cambia su API de cliente, lo más probable es que tenga un nombre diferente y forme una plantilla diferente. . Todas las ventajas adicionales que pueden hacer las plantillas se agregaron principalmente al conjunto de herramientas porque ... en C ++ no había reflejo y tenía que ser emulado de alguna manera. SFINAE es una forma de reflexión en tiempo de compilación.

Por lo tanto, el jugador más grande en el mundo de las diferencias se reduce a un efecto secundario curioso (beneficioso) de un hotfix aplicado para enmascarar la deficiencia del tiempo de ejecución, que es la casi completa falta de introspección del tiempo de ejecución :))

Por lo tanto, digo que no hay otra diferencia que algunas arbitrarias aplicadas por laguage, o algunas arbitrarias aplicadas por la plataforma de tiempo de ejecución.

Todos ellos son solo una forma de clases o funciones / métodos de orden superior, y creo que esta es la característica y característica más importante.


Limitaré mis respuestas a las plantillas de C ++ frente a los genéricos de Java.

  1. Las plantillas C ++ (plantillas de clase y plantillas de funciones) son mecanismos para implementar el polimorfismo en tiempo de compilación, pero los genéricos Java de AFAIK son mecanismos en tiempo de ejecución.
  2. Al usar plantillas de C ++, puede hacer programación genérica y en realidad es un estilo / paradigma de programación totalmente separado, pero los genéricos de Java son un estilo OO per se. vea abajo:
  3. Las plantillas de C ++ se basan en la tipificación Duck, pero los genéricos de Java se basan en el Borrado de tipos. En C ++, el vector<int> , vector<Shape *> , el vector<double> y el vector<vector<Matrix>> son 4 tipos distintos, pero en Java Cell<int> , Cell<Integer> Cell<double> y Cell<Matrix> son del mismo tipo. Más precisamente, durante la generación de código, el compilador borra el tipo en primer lugar. Puede comprobarlo siguiendo el código del siguiente artículo: Vladimir Batov. Java genéricos y plantillas de C ++. C / C ++ Users Journal, julio de 2004.

    public class Cell<E> { private E elem; public Cell(E elem) { this.elem = elem; } public E GetElement() { return elem; } public void SetElement(E elem) { this.elem = elem; } } boolean test() { Cell<Integer> IntCell(10); Cell<String> StrCell(“Hello”); return IntCell.getClass() == StrCell.getClass(); // returns true }

En resumen, Java pretende ser genérico, mientras que C ++ en realidad lo es.


Primero, me parece interesante que RTTI / introspección sea una parte importante de la mayoría de las respuestas. Bueno, eso no es una diferencia entre los genéricos y las plantillas, sino los idiomas con instrospección y los idiomas que no lo tienen. De lo contrario, también puede afirmar que para ser una diferencia de clases de C ++ con clases de Java y funciones de C ++ con funciones de Java ...

Si elimina la instrospección, la diferencia principal es que las plantillas definen un lenguaje completo y funcional, aunque con una gramática horrible en la que puede programar . El primer ejemplo realmente complejo del que escuché (me encantaría tener el código, pero no lo creo) fue un programa que calculaba los números primos en el momento de la compilación . Lo que aporta otra diferencia: las plantillas pueden tomar argumentos de tipo, argumentos de plantilla o argumentos de no tipo ( no de tipo se refiere a cualquier cosa que no sea un tipo o una plantilla, como un valor int ).

Esto se ha mencionado en otras respuestas, pero solo decir que las plantillas pueden ser especializadas y que existe SFINAE no establece claramente que esas dos características son suficientes para generar un lenguaje completo y completo.


no, las plantillas no son un superconjunto de genéricos, con las plantillas de C ++ que no tiene soporte de tiempo de ejecución al mismo nivel que con los genéricos de C #, lo que significa que RTTI en C ++ no puede detectar y le proporciona metadatos de plantillas como Reflection para genéricos en c #.

Al lado de esto, me gusta este fragmento:

Las plantillas de C ++ utilizan un modelo en tiempo de compilación. Cuando se usa una plantilla en un programa C ++, el efecto es como si se hubiera usado un procesador de macros sofisticado.

Los genéricos de C # no son solo una característica del compilador, sino también una característica del tiempo de ejecución. Un tipo genérico como List mantiene su genérico (genérico) después de que se haya compilado. O, para verlo de otra manera, la sustitución que hace el compilador de C ++ en tiempo de compilación se realiza en el momento JIT en el mundo genérico de C #.

Consulte aquí el artículo completo: ¿Cómo se comparan los genéricos de C # con las plantillas de C ++?