tourette the sobre sindrome resumen preguntas pelicula online latino front frente español dela critico completa clase analisis c++ templates code-readability

c++ - the - pelicula sindrome de tourette netflix



Definición fuera de línea de la función de la plantilla frente a la clase (4)

¿Hay funciones de idioma que sean más fáciles de usar con la primera o la segunda versión?

Un caso bastante trivial, pero vale la pena mencionarlo: especializaciones .

Como ejemplo, puede hacer esto con una definición fuera de línea:

template<typename T> struct MyType { template<typename... Args> void test(Args...) const; // Some other functions... }; template<typename T> template<typename... Args> void MyType<T>::test(Args... args) const { // do things } // Out-of-line definition for all the other functions... template<> template<typename... Args> void MyType<int>::test(Args... args) const { // do slightly different things in test // and in test only for MyType<int> }

Si desea hacer lo mismo con las definiciones en la clase solamente, debe duplicar el código para todas las otras funciones de MyType (suponiendo que la test es la única función que desea especializar, por supuesto).
Como ejemplo:

template<> struct MyType<int> { template<typename... Args> void test(Args...) const { // Specialized function } // Copy-and-paste of all the other functions... };

Por supuesto, todavía puede mezclar las definiciones en clase y fuera de línea para hacer eso y tiene la misma cantidad de código de la versión completa fuera de línea.
De todos modos, asumí que estás orientado hacia soluciones completas en línea y completas fuera de línea, por lo tanto, las mixtas no son viables.

Otra cosa que puede hacer con las definiciones de clase fuera de línea y que no puede hacer con las definiciones en clase es la especialización de plantilla de función.
Por supuesto, puede poner la definición principal en la clase, pero todas las especializaciones deben ponerse fuera de línea.

En este caso, la respuesta a la pregunta mencionada anteriormente es: existen funciones uniformes del idioma que no se pueden usar con una de las versiones .

Como ejemplo, considere el siguiente código:

struct S { template<typename> void f(); }; template<> void S::f<int>() {} int main() { S s; s.f<int>(); }

Supongamos que el diseñador de la clase quiere proporcionar una implementación para f solo para algunos tipos específicos.
Simplemente no puede hacer eso con definiciones en clase.

Finalmente, las definiciones fuera de línea ayudan a romper las dependencias circulares.
Esto ya se mencionó en la mayoría de las otras respuestas y no vale la pena dar otro ejemplo.

Me preguntaba si había ventajas de declarar plantillas fuera de línea vs en la clase.

Estoy tratando de obtener una comprensión clara de los pros y los contras de las dos sintaxis.

Aquí hay un ejemplo:

Fuera de línea:

template<typename T> struct MyType { template<typename... Args> void test(Args...) const; }; template<typename T> template<typename... Args> void MyType<T>::test(Args... args) const { // do things }

Vs en clase:

template<typename T> struct MyType { template<typename... Args> void test(Args... args) const { // do things } };

¿Hay funciones de idioma que sean más fáciles de usar con la primera o la segunda versión? ¿La primera versión se interpondrá cuando se usen argumentos de plantilla predeterminados o enable_if? Me gustaría ver comparaciones de cómo están jugando esos dos casos con diferentes características de lenguaje como sfinae, y tal vez posibles características futuras (¿módulos?).

Tener en cuenta el comportamiento específico del compilador también puede ser interesante. Creo que MSVC necesita estar en inline en algunos lugares con el primer fragmento de código, pero no estoy seguro.

EDITAR: Sé que no hay diferencia en cómo funcionan estas características, que esto es principalmente una cuestión de gusto. Quiero ver cómo ambas sintaxis juegan con diferentes técnicas y la ventaja de una sobre la otra. Veo en su mayoría respuestas que favorecen a uno sobre otro, pero realmente quiero obtener ambos lados. Una respuesta más objetiva sería mejor.


No hay diferencia entre las dos versiones con respecto a los argumentos de la plantilla predeterminada, SFINAE o std::enable_if ya que la resolución de sobrecarga y la sustitución de los argumentos de la plantilla funcionan de la misma manera para ambos. Tampoco veo ninguna razón por la que debería haber una diferencia con los módulos, ya que no cambian el hecho de que el compilador necesita ver la definición completa de las funciones miembro de todos modos.

Legibilidad

Una de las principales ventajas de la versión fuera de línea es la legibilidad. Puede simplemente declarar y documentar las funciones miembro e incluso mover las definiciones a un archivo separado que se incluye al final. Esto hace que el lector de su plantilla de clase no tenga que omitir una cantidad potencialmente grande de detalles de implementación y pueda simplemente leer el resumen.

Para su ejemplo particular, podría tener las definiciones

template<typename T> template<typename... Args> void MyType<T>::test(Args... args) const { // do things }

en un archivo llamado MyType_impl.h y luego el archivo MyType.h contiene solo la declaración

template<typename T> struct MyType { template<typename... Args> void test(Args...) const; }; #include "MyType_impl.h"

Si MyType.h contiene suficiente documentación de las funciones de MyType mayoría de las veces los usuarios de esa clase no necesitan ver las definiciones en MyType_impl.h .

Expresividad

Pero no es solo la mayor legibilidad lo que diferencia las definiciones fuera de línea y en clase. Si bien cada definición en clase se puede mover fácilmente a una definición fuera de línea, lo contrario no es cierto. Es decir, las definiciones fuera de línea son más expresivas que las definiciones en clase. Esto sucede cuando tiene clases estrechamente acopladas que dependen de la funcionalidad del otro para que una declaración directa no sea suficiente.

Uno de estos casos es, por ejemplo, el patrón de comando si se desea que admita el encadenamiento de comandos y que sea compatible con las funciones definidas por el usuario y los funtores sin que tengan que heredar de alguna clase base. Entonces tal Command es esencialmente una versión "mejorada" de std::function .

Esto significa que la clase Command necesita alguna forma de borrado de tipo que voy a omitir aquí, pero puedo agregarlo si alguien realmente quisiera que lo incluyera.

template <typename T, typename R> // T is the input type, R is the return type class Command { public: template <typename U> Command(U const&); // type erasing constructor, SFINAE omitted here Command(Command<T, R> const&) // copy constructor that makes a deep copy of the unique_ptr template <typename U> Command<T, U> then(Command<R, U> next); // chaining two commands R operator()(T const&); // function call operator to execute command private: class concept_t; // abstract type erasure class, omitted template <typename U> class model_t : public concept_t; // concrete type erasure class for type U, omitted std::unique_ptr<concept_t> _impl; };

Entonces, ¿cómo implementarías .then ? La manera más fácil es tener una clase de ayuda que almacena el Command original y el Command para ejecutar después de eso y simplemente llama a ambos de sus operadores de llamadas en secuencia:

template <typename T, typename R, typename U> class CommandThenHelper { public: CommandThenHelper(Command<T,R>, Command<R,U>); U operator() (T const& val) { return _snd(_fst(val)); } private: Command<T, R> _fst; Command<R, U> _snd; };

Tenga en cuenta que Command no puede ser un tipo incompleto en el punto de esta definición, ya que el compilador necesita saber que Command<T,R> y Command<R, U> implementan un operador de llamada así como su tamaño, por lo que una declaración forward es no suficiente aquí. Incluso si tuviera que almacenar los comandos de miembro por puntero, para la definición de operator() necesita absolutamente la declaración de Command .

Con esta ayuda podemos implementar Command<T,R>::then :

template <typename T, R> template <typename U> Command<T, U> Command<T,R>::then(Command<R, U> next) { // this will implicitly invoke the type erasure constructor of Command<T, U> return CommandNextHelper<T, R, U>(*this, next); }

De nuevo, tenga en cuenta que esto no funciona si CommandNextHelper solo se declara como forward porque el compilador necesita conocer la declaración del constructor para CommandNextHelper . Como ya sabemos que la declaración de Command de clase debe venir antes de la declaración de CommandNextHelper , esto significa que simplemente no puede definir la función .then en la clase. La definición de esto tiene que venir después de la declaración de CommandNextHelper .

Sé que este no es un ejemplo simple, pero no podría pensar en uno más simple porque ese problema aparece principalmente cuando es absolutamente necesario definir algún operador como miembro de la clase. Esto se aplica principalmente al operator() y al operator[] en las plantillas de expesión ya que estos operadores no se pueden definir como no miembros.

Conclusión

Entonces, para concluir: es sobre todo una cuestión de gusto cuál prefieres, ya que no hay mucha diferencia entre los dos. Solo si tiene dependencias circulares entre clases no puede usar la definición en clase para todas las funciones miembro. Personalmente, prefiero las definiciones fuera de línea, ya que el truco para externalizar las declaraciones de funciones también puede ayudar con las herramientas de generación de documentación como doxygen, que luego solo creará la documentación para la clase real y no para los ayudantes adicionales que están definidos y declarados en otro archivo.

Editar

Si entiendo correctamente su edición a la pregunta original, le gustaría ver cómo se ven los parámetros generales SFINAE, std::enable_if y plantilla predeterminada para ambas variantes. Las declaraciones se ven exactamente iguales, solo para las definiciones debe eliminar los parámetros predeterminados, si los hay.

  1. Parámetros de plantilla predeterminados

    template <typename T = int> class A { template <typename U = void*> void someFunction(U val) { // do something } };

    vs

    template <typename T = int> class A { template <typename U = void*> void someFunction(U val); }; template <typename T> template <typename U> void A<T>::someFunction(U val) { // do something }

  2. enable_if en el parámetro de plantilla predeterminado

    template <typename T> class A { template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>> bool someFunction(U const& val) { // do some stuff here } };

    vs

    template <typename T> class A { template <typename U, typename = std::enable_if_t<std::is_convertible<U, T>::value>> bool someFunction(U const& val); }; template <typename T> template <typename U, typename> // note the missing default here bool A<T>::someFunction(U const& val) { // do some stuff here }

  3. enable_if como parámetro de plantilla sin tipo

    template <typename T> class A { template <typename U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0> bool someFunction(U const& val) { // do some stuff here } };

    vs

    template <typename T> class A { template <typename U, std::enable_if_t<std::is_convertible<U, T>::value, int> = 0> bool someFunction(U const& val); }; template <typename T> template <typename U, std::enable_if_t<std::is_convertible<U, T>::value, int>> bool A<T>::someFunction(U const& val) { // do some stuff here }

    De nuevo, solo falta el parámetro predeterminado 0.

  4. SFINAE en tipo de retorno

    template <typename T> class A { template <typename U> decltype(foo(std::declval<U>())) someFunction(U val) { // do something } template <typename U> decltype(bar(std::declval<U>())) someFunction(U val) { // do something else } };

    vs

    template <typename T> class A { template <typename U> decltype(foo(std::declval<U>())) someFunction(U val); template <typename U> decltype(bar(std::declval<U>())) someFunction(U val); }; template <typename T> template <typename U> decltype(foo(std::declval<U>())) A<T>::someFunction(U val) { // do something } template <typename T> template <typename U> decltype(bar(std::declval<U>())) A<T>::someFunction(U val) { // do something else }

    Esta vez, dado que no hay parámetros predeterminados, tanto la declaración como la definición tienen el mismo aspecto.


Separar la declaración de la implementación le permite hacer esto:

// file bar.h // headers required by declaration #include "foo.h" // template declaration template<class T> void bar(foo); // headers required by the definition #include "baz.h" // template definition template<class T> void bar(foo) { baz(); // ... }

Ahora, ¿qué haría esto útil? Bueno, el encabezado baz.h ahora puede incluir bar.h y depender de la bar y otras declaraciones, aunque la implementación de la bar depende de baz.h

Si la plantilla de función se definió en línea, debería incluir baz.h antes de declarar la bar , y si baz.h depende de la bar , entonces tendría una dependencia circular.

Además de resolver dependencias circulares, definir funciones (ya sea de plantilla o no) fuera de línea, deja las declaraciones en una forma que funciona efectivamente como una tabla de contenido, que es más fácil de leer para los programadores que las declaraciones salpicadas en un encabezado lleno de definiciones . Esta ventaja disminuye cuando utiliza herramientas de programación especializadas que proporcionan una visión general estructurada del encabezado.


Tiendo a fusionarlos siempre, pero no puedes hacer eso si son codependientes. Para el código regular, generalmente coloca el código en un archivo .cpp, pero para las plantillas, todo el concepto no se aplica realmente (y crea prototipos de funciones repetidas). Ejemplo:

template <typename T> struct A { B<T>* b; void f() { b->Check<T>(); } }; template <typename T> struct B { A<T>* a; void g() { a->f(); } };

Por supuesto, este es un ejemplo artificial pero reemplaza las funciones con otra cosa. Estas dos clases se requieren mutuamente para ser definidas antes de que puedan ser utilizadas. Si usa una declaración forward de la clase de plantilla, aún no puede incluir la implementación de la función para uno de ellos. Esa es una gran razón para ponerlos fuera de línea, lo cual lo soluciona al 100% todo el tiempo.

Una alternativa es hacer que una de estas sea una clase interna de la otra. La clase interna puede llegar a la clase externa más allá de su propio punto de definición para las funciones, por lo que el problema está oculto, lo que se puede usar en la mayoría de los casos cuando se tienen estas clases dependientes.