Sobre el problema de la implementación de funciones de base de datos "Fan Out" - parte III

Omer Pinto
Datos
June 8, 2021

Sobre el problema de la implementación de funciones de base de datos "Fan Out" - parte III

En la parte I de este artículo, presentamos el problema de la función de bases de datos en fan out, y pensamos que cada función de este tipo es en realidad una multifunción con 2^n versiones, dadas n entradas. En la parte II de este artículo, hemos visto algunas soluciones al problema (también hemos considerado dos bases de datos de código abierto MonetDB y ClickHouse). Antes de presentar mi solución en la parte IV, he decidido redactar un artículo preliminar (este), para tener la oportunidad de presentar en mayor detalle algunas de las técnicas y clases de TMP que se usaré para elaborar mi solución.

Otra ventaja de este enfoque es la introducción de un código mucho más fácil de comprender en el próximo artículo. Si el lector internaliza las técnicas y clases introducidas en esta parte, estará mucho más preparado para comprender la solución detallada presentada en la siguiente parte. He de admitir que es compleja, pero creo que con un enfoque estructurado y gradual, es implementable y vale la pena.

Lógicamente no voy a poder explicar todo el contenido necesario sobre plantillas C++ y TMP desde el principio. Es demasiada información y no es el propósito de este artículo. Intentaré explicar todas las técnicas avanzadas utilizadas, pero tener un conocimiento previo sobre las plantillas variadic (variadic templates) es una ventaja. Antes de comenzar con el artículo, me gustaría recomendar un breve texto0 que presenta TMP de una manera única que podría ser beneficiosa para nuestro propósito. (va directo al grano y puede ser de gran ayuda para empezar a usar TMP en tus programas):

No alt text provided for this image

Los autores Edouard Alligand y Joel Falcou se refieren a él como un “informe de metaprogramación en C++” (escrito después de la introducción de C++ 14). Si quieres una discusión más completa sobre el tema, te recomiendo “Plantillas C++” (“C++ Templates”) de David Vandevoorde, Nicolai Josuttis & Douglas Gregor (Biblia TMP, segunda versión).

Introducción de std::integer_sequence

En la metaprogramación de plantillas no existe una construcción iterativa. No puedes iterar en una lista de tipos (como los tipos dentro de std::tuple) con un bucle for o while, como lo harías con el código que se ejecuta en tiempo de ejecución (runtime). Sin embargo, puedes utilizar la recursión para acceder a todos los tipos de la lista y decidir qué quieres hacer con ellos. Veamos un ejemplo:

No alt text provided for this image

Este es un ejemplo simple de una clase de plantilla que puede contener un std::tuple de cualquier tipo e indica una manera para imprimirlo elemento a elemento. La clase está basada en los tipos dentro de la tupla (Args) usando una plantilla variadic. Centrémonos primero en las implementaciones print_tuple (línea 38) y print_tuple_impl (línea 18) (vamos a ignorar las versiones eficientes por ahora).

print_tuple_impl es una función de plantilla (template function), que está basada en el índice de la tupla: size_t k. Para cada uno de esos k, la función imprime el k-ésimo elemento de la tupla y llama de forma recursiva a la siguiente versión de la función (k + 1).

La recursividad se detiene cuando k es igual al tamaño de la tupla menos 1 (para una tupla tup de tamaño n, podemos llamar std::get<i>(tup) para cada 0 <= i <n). Para verificar el valor constante usamos la nueva funcionalidad de C++ 17 if constexpr que nos permite evaluar si llegamos a la condición de detención de la recursividad. En este tipo de condición, solo debemos usar valores declarados como constexpr y conocidos en tiempo de compilación (no es la construcción if normal del tiempo de ejecución). Cuando llegamos al último elemento, lo imprimimos y terminamos (evitando una nueva llamada recursiva). Finalmente, la función pública "wrapper" print_tuple, llama a print_tuple_impl <0> para comenzar a imprimir la tupla desde el primer índice.

Muy bien, podrías pensar, ¿y esto qué nos aporta? ¿Por qué no usamos un bucle for “normal”? Puedes intentar hacer algo como esto:

No alt text provided for this image

Cuando intentes llamar a esta función (con cualquier tupla como argumento), obtendrás una salida horrible con muchos errores de tiempo de compilación (compile time errors). Podrás observer que el error indica lo siguiente "nota: la deducción / sustitución del argumento de plantilla falló: error: el valor de 'i' no se puede utilizar en una expresión constante" ("note:  template argument deduction/substitution failed: error: the value of ‘i’ is not usable in a constant expression"). En pocas palabras: no se puede usar el índice de bucle en ejecución (running loop index) como una constante en la expresión std::get <i> (t). La razón de esto es que std::get está basado en el argumento de la plantilla size_t, y para que el compilador cree una instancia y llame a la función correcta, necesita saber en el momento de la compilación cuál es el valor de i. Espero haberte convencido de que necesitamos TMP (como en la solución anterior).

El enfoque recursivo (recursive approach) que se muestra arriba se ha utilizado desde los 2000 en varios casos y fue bastante poderoso. Sin embargo, tiene dos desventajas, las mismas que se emplean en contra de TMP en cada argumento: aumenta la presión sobre nuestro binario compilado y extiende significativamente nuestro tiempo de compilación. De hecho, si no es suficiente que estamos creando una clase por tipo de tupla (ya que cada tipo de tupla diferente creará una instancia de la clase usando el parámetro de plantilla Args de manera diferente), generamos una gran cantidad de funciones "intermedias" de print_tuple_impl (la cantidad es igual al tamaño de la tupla ...).

¡Esto debe evitarse a toda costa! A nadie le gustan las largas horas de compilación y los grandes binarios.

Echemos un vistazo a una alternativa. Para su desarrollo, usaré una clase de C ++ 14 llamada std::integer_sequence. Esta clase usa una plantilla variada de enteros (integers) para proporcionarnos una lista de enteros consecutivos que podrían usarse en tiempo de compilación. Por ejemplo, si llamas: std::make_index_sequence<10>{}, se generará la secuencia 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Una vez que esa secuencia está disponible como una constante de tiempo de compilación (compile-time constant), podemos usarla con la expansión de plantilla variac para imprimir nuestra tupla sin recursividad:

  • Echa un vistazo a print_tuple_efficient_impl en la línea 30 en la figura anterior. Queremos que este método obtenga una std::index_sequence con la longitud de la tupla. Así que lo escribimos como una función de plantilla (template function) con el parámetro de plantilla variable (variadic template parameter) I de tipo size_t (plantilla <size_t ... I>), que toma como entrada (input) un parámetro del tipo de la propia secuencia: std::index_sequence <I ...>.
  • Después, usamos una nueva característica de C++17, llamada expresión de pliegue (fold expression) para imprimir los valores de la tupla con una expansión variadic en la línea 32. Esto es un poco más complicado, pero la idea es que la expresión interna (std :: cout << std :: get <I> (_tuple) << ",") se ejecute para "cada I" que va desde 0 hasta la longitud de la tupla menos 1 (usando el operador de expansión ...). Hay algunas reglas de asociatividad que puedes consultar en el enlace de arriba, pero básicamente la expresión de pliegue te permite reducir un paquete de parámetros (I en ese caso) sobre operaciones unarias / binarias (en ese caso, la operación std::cout dentro del paréntesis interno). Por tanto, de esta manera puedes ejecutar la función de reducción (reduce function) que quizás conozcas de tu lenguaje funcional favorito usando estructuras en tiempo de compilación.
  • Por último, puedes usar la función pública que envuelve esta metafunción, llamada print_tuple_efficient (línea 44). Esta función llama a la función de plantilla std::make_index_sequence con el tamaño de la tupla (¡un valor constexpr!) Para llamar a la función interna print_tuple_efficient_impl con un parámetro de tipo std :: index_sequence <I ...>, cuando I = el tamaño de la tupla.

Esta técnica nos permite implementar la misma funcionalidad que antes sin generar tipos intermedios innecesarios. Siempre que sea posible, se debe usar el operador ... para aplicar un callable a cada miembro de un tipo de tiempo de compilación variadic, y no usar estructuras recursivas. Esto se traduce en un tiempo de compilación menor, no se generan tipos intermedios innecesarios, el código es más breve y el tamaño binario es menor (por tanto otro argumento en contra de TMP se va a la basura :) ).

 

De vuelta a nuestro caso

Ahora que hemos entendido la técnica de std::make_index_sequence, estamos listos para echar un vistazo a nuestros bloques de construcción para la solución TMP para el problema de distribución de la base de datos. Como expliqué en la primera parte, cuando tratamos con una multifunción con n entradas, podemos usar una tabla de verdad de n bits (cuyo tamaño es 2^n) para ayudarnos a identificar cada una de las 2^n versiones diferentes de la función. Por ejemplo, para la función subString con la firma  subString(string, int, int), tendremos la siguiente tabla:

truth table

 

Por lo tanto, obtenemos que cada parámetro con valor 0 en su columna es una entrada de parámetro constante (valor único), y cada parámetro con valor 1 es un vector. Observa que el bit más significativo es el último parámetro (el tercero a la derecha en la firma de la función) y el bit menos significativo es el primer parámetro. Esta representación de las diferentes versiones de la función subString puede darnos una pista sobre lo que podríamos tener que proporcionar a nuestra metafunción para admitir diferentes versiones en un solo lugar. Mirando los bits en la tabla, obtenemos los números binarios 000-111, que son los números 0-7. Si pudiéramos proporcionar a nuestra función el número de versión (que es una constante en tiempo de compilación), podría usarlo para adaptar la implementación a la versión de subString escrita en la última columna de la tabla anterior. Esta función inteligente sabrá cómo obtener "polimórficamente" cada tipo de parámetro de entrada de acuerdo con su bit (ya sea constante o vectorial) e implementar la funcionalidad de subString solo una vez. Este es un "polimorfismo" en tiempo de compilación.

Pero eso es para otro día: la solución completa para eso se mostrará en la parte IV de este artículo. Por ahora, hagámonos la siguiente pregunta: ¿Cómo podemos usar la idea abstracta del número de versión presentada en la tabla anterior para definir la entrada polimórfica de subString (8 versiones diferentes)? Me gustaría presentar dos posibles soluciones diferentes en detalle. Aunque eventualmente implementaré solo una de ellas en mi propia solución, creo que es útil examinar ambas.

Divisor de tuplas (Tuple Splitter) - introducción del concepto

Dada una tupla que contiene los tipos de entrada de subString (en el mismo orden): std::tuple <string, int, int> y un número de versión (al que prefiero llamar “Mask” (máscara), pronto entenderás por qué) entre 0 y 7, puedo usar este mask para dividir la tupla en 2 tuplas separadas: una representará una tupla de constantes y la otra representará una tupla de vectores. ¿Suena complejo? La idea es muy simple. Veamos el siguiente ejemplo (lee la operación '&' a continuación, columna por columna):

input: std::tuple<std::string, int, int>, Mask = 0b101 output:


1)vector_tuple (inclusive) = String, int, int
                             &      &    &
                             0      1    1
                         = std::tuple<int, int>
2) constant_tuple (exclusive) = String, int, int
                                &      &    &
                               ~0     ~1   ~1
                            = std::tuple<string>

Ejecutamos la máscara sobre la lista de tipos dentro de la tupla (en std::tuple <x, y, z>, se obtiene x en el índice 0 y z en el índice 2. Por eso escribimos la máscara al revés en la imagen de arriba). El resultado de aplicar la mascara es el resultado de la tupla vectorial (011 indica que este vector incluirá 2 enteros). El uso de ~Mask (operador “no”) nos da la tupla escalar (que incluirá solo una cadena). Generalmente, tendremos lo siguiente para todos los posibles valores de Mask:

No alt text provided for this image

Ten en cuenta que cada línea es diferente. Incluso si las líneas 4 y 6 parecen iguales, no lo son: aunque los parámetros 2 y 3 tienen el mismo tipo (int), siguen siendo 2 parámetros diferentes en la función subString con un significado diferente.

Ahora, suponiendo que podamos implementar este divisor, ¿cómo lo podemos usar? Una vez hayamos dividido la entrada, podríamos decidir implementar un método/clase de plantilla que obtendrá las 2 tuplas de entrada por separado para luego entrelazarlas en una sola entrada (siguiendo el orden original), como en la versión escalar de subString. Esto podría ser útil en los casos en los que la versión subString se ejecuta muchas veces con las mismas constantes de entrada pero con diferentes vectores de entrada (piensa en un vector de entrada de tamaño 10^7 que hemos decidido dividir en 1000 grupos de tamaño 10,000 ejecutando subString en cada grupo por separado). En ese caso, podríamos haber utilizado el divisor de tuplas para poder obtener la tupla de constantes una sola vez (por ejemplo a través de un constructor de una clase contenedora (wrapper class)) y los diferentes vectores de entrada a través de parámetros de función (diferentes en cada llamada). Ahora no nos vamos a ocupar del uso de ese código, solo de su implementación.

 

Divisor de tuplas (Tuple Splitter) – implementación en C++ 

Nota: La solución presentada aquí y también la de la siguiente sección las puedes encontrar en el proyecto TMP-Tutorial en mi git. Incluye la plantilla de clases de metaprogramación y algunos tests que muestran cómo utilizarlas.

Echemos un vistazo a la solución del divisor de tuplas:

No alt text provided for this image

Como antes, tenemos una especie de clase contenedora (como si tuviéramos una función contenedora) y una clase interna. También usamos la técnica std::index_sequence presentada anteriormente para recorrer la lista de tipos de la tupla. Respira hondo y profundicemos en la solución:

  • Primero declaramos una estructura TupleSplitter en la línea 20 que tiene una plantilla en un Type (se usará para contener la tupla de entrada completa), size_t Mask y otro type_I. Escribimos esta estructura general sin implementación, por lo que podríamos usar la especialización de clase de plantilla (template class specialization) en las líneas 23-35 y obtener los tipos que necesitamos (es decir, std::tuple<Types...> y std::index_sequence<I...>).
  • Después, en las líneas 23-35 utilizamos una especialización de plantilla (template specialization): obtenemos un tipo variadic “types” (dentro de una tupla en la especialización), un Mask de tipo size_t para nuestro mask, y un tipo variadic de I (dentro de una index_sequence en la especialización).
  • Usamos la noción de metafunción (de Boost.MPL), "una clase o una plantilla de clase invocable en tiempo de compilación". Dichas metafunciones pasan sus entradas como parámetros de plantilla y su(s) tipo(s) de retorno como definición(es) de tipo interno. Por lo tanto, dentro de la clase declaramos tipos internos con la cláusula using. Estos son los resultados del metaprograma y son equivalentes a un valor de retorno de una función. Entonces, en lugar de una función con un tipo de retorno (return type), tenemos un metaprograma (TypeSplitter) con tipos de resultados (results types).
  • Hay 3 tipos definidos: un tipo auxiliar llamado tupleType en la línea 26 usado para contener el tipo de tupla completo (std::tuple<Types...>) y otros 2 tipos llamados inclusive & exclusive, que definen la tupla vectorial y la tupla escalar como se presenta arriba. Estos son la esencia de esta solución y analizararemos su código a continuación.
  • Nos centraremos en la definición inclusive en las líneas 27-30 (la definición exclusive es muy similar). La base de esta la implementación está el uso de std::conditional, que se usa para decidir entre 2 tipos. Su definición es: template< bool B, class T, class F > struct conditional, y en cppreference su documentación explica lo siguiente “Proporciona el tipo typedef de cada miembro, que se define como T si B es verdadero en tiempo de compilación, o como F si B es falso” (“Provides member typedef type, which is defined as T if B is true at compile time, or as F if B is false”). Por tanto, si la condición ((Mask >> I) & 0x1) es verdadera, obtenemos una tupla con un solo tipo: el tipo de la tupla de entrada en el índice actual; y si es falsa, obtenemos una tupla vacía.
  • La condición realmente usa la máscara como se sugirió anteriormente: realiza una comparación con los índices 0 <= i <input_tuple_size, para verificar si la máscara contiene '1' en el lugar relevante (desplazamos hacia la derecha la máscara i posiciones y AND el último bit usando otra máscara 0x01). Si es así, el tipo relevante (relevant type), dado por el rasgo: typename std::tuple_element<I, tupleType>::type se inserta en la tupla de tipo único (básicamente std::tuple_element obtiene un índice constante I y devuelve el tipo número I dentro de la tupla dada por tupleType, definido anteriormente). Si contiene '0', entonces el resultado de la condición de tiempo de compilación será tuple<>.
  • El resultado de std::conditional se recopila en la línea 29, usando ::type después del corchete de cierre de la plantilla angular de la condición (>). A continuación, inicia un objeto de ese tipo usando {}.
  • Por lo tanto, cada una de estas ejecuciones std::conditional evalúa la máscara necesaria para determinar si el tipo en ese índice "debería aparecer" en la tupla del vector de resultados. Pero, ¿cómo "recopilamos" todos estos resultados separados en una tupla? Como antes, usamos la expansión de plantilla variadic ..., esta vez con la ayuda de la función std::tuple_cat. ¡Esta función construye una tupla que es la concatenación de todas las tuplas en su entrada! Por tanto usamos la expansión ... para llenar std::tuple_cat con las entradas necesarias (esta función, como era de esperar, ignora las tuplas vacías, ¡justo lo que necesitábamos!). Ten en cuenta que esta función en realidad obtiene como entrada tuplas, y no tipos de tuplas. Es por eso por lo que instanciamos estas tuplas usando {} como expliqué anteriormente.
  • Para definir un tipo (que eventualmente es nuestro propósito), usamos decltype en la línea 27 para obtener el tipo de resultado de la llamada a std::tuple_cat, y este es nuestro tipo de resultado inclusive.

Antes de pasar al tipo de contenedor externo (outer wrapper type) y concluir la implementación, echemos un vistazo a lo que sucede (¡en tiempo de compilación!) en este complejo código de tipo inclusive en un ejemplo. Supongamos que llamamos a TupleSplitter con std::tuple<int, string, double>, 0b110 y std::make_index_sequence<3>:

  1. Para el índice 0 tendremos los resultados de la condición: (110 >> 0) & 0x1 = 0 y terminaremos con la tupla vacía.
  2. Para el índice 1 tendremos la condición result (110 >> 1) & 0x1 = 11 & 01 = 1, y terminaremos con la tupla: tuple <string>.
  3. Para el índice 2 tendremos la condición resultado (110 >> 2) & 0x1 = 1 & 1 = 1, y terminaremos con la tupla: tuple <double>.
  4. Finalmente, concatenamos todos (usando la concatenación de tuplas (tuple concatenation)) al resultado de inclusive = std :: tuple <string, double> que es la tupla vectorial que queríamos obtener con esta máscara. Exclusive comprueba la condición opuesta (busca cuando la máscara es igual a 0) y terminará con: ttuple<int>.

Concluyamos la discusión sobre TupleSplitter examinando el tipo de contenedor TupleSplitterWrapper. Nuevamente, no hay nada nuevo aquí: usamos la misma técnica de especialización de plantilla para los tipos de tipo variadic que se incluirán dentro de una tupla (designa la entrada de tupla completa). Además, como en el primer ejemplo, usamos std::make_index_sequence, esta vez con el tamaño de la tupla (dado al aplicar el operador sizeof ... en el paquete de parámetros: sizeof ... (Types)). Este contenedor llama a la clase interna TupleSplitter con las dos plantillas variadas necesarias para poder realizar su tarea. Finalmente, la clase contenedora define sus propios tipos para exponer los tipos de clases internas: inclusiveType & exclusiveType.

Antes de pasar al siguiente caso de uso, veamos cómo podemos comprobar (¡en tiempo de compilación!) que nuestra clase realmente funciona. Para ello, echa un vistazo al archivo TupleSplitterTest.cpp en mi Git. Hay muchos ejemplos / tests allí y aquí me voy a referir en uno solo:

No alt text provided for this image

Usamos una tupla de longitud 4 (std::tuple <int, float, double, bool>) como la tupla de entrada completa y llamamos al contenedor (wrapper) con la máscara 0b1010. Recopilamos los tipos internos (inclusiveType y exclusiveType) y usamos static_assert (una prueba en tiempo de compilación que falla en la compilación en caso de que falle la condición comprobable en tiempo de compilación) con rasgo std::is_same, para probar si estos tipos son iguales a lo que esperamos. La máscara sugiere que nos gustaría hacer una división entre el 1er y el 3er tipo de tupla (marcados en la máscara con ceros, por lo que estarán solo en el tipo exclusivo: la tupla escalar) y tipos de tupla de 2 y 4 (marcados en la máscara como unos, por lo que estarán solo en el tipo inclusivo: la tupla vectorial). Puede ver que este es el caso en las líneas 29-30. Las pruebas adicionales en el archivo también muestran un ejemplo "completo" de una tupla de tamaño 3 (como en Substring) y examina todas las máscaras posibles aplicadas sobre ellas usando TupleSplitterWrapper.

 

Con esto concluye el ejemplo de TupleSplitter.

 

Functor Input Setter - introducción al concepto

Este ejemplo es bastante similar al anterior y, por lo tanto, lo presentaremos de manera mucho más breve. Puede encontrar el código completo en FunctorInputSetter.h en mi Git (incluidas las pruebas en FunctorInputSetterTester.cpp). Aquí implementaremos una clase de metaprograma que realmente construirá el tipo de entrada como se describe en el ejemplo en la primera imagen de tabla de subString (usando tipos y vector de tipos en la misma tupla). Concretamente: dada una tupla de entradas y un número de versión (nuevamente usando mask) entre 0 y el tamaño de la tupla - 1, uso esta máscara para obtener una nueva tupla única de la misma longitud. En esta ocasión la máscara decide para cada tipo T, si incluir T en la tupla de resultado (es decir, T representa un elemento de entrada constante) o si incluir std::vector<T> (es decir, T representa un elemento de entrada de vector).

Esta clase es más apropiada para un caso de uso en el que nos gustaría ejecutar la función una vez (no ejecutarlo una vez por grupo, para varios grupos, como se sugiere en el ejemplo anterior del divisor de tuplas). En ese caso, en lugar de dividir la entrada, la transformamos plolimórficamente en tiempo de compilación en la entrada requerida dada por la función version(mask). Además, esta será la versión utilizada como componente básico de mi solución que se presentará en la parte IV de este artículo.

Functor Input Setter – implementación en C++ 

Echemos un breve vistazo al código (es muy similar a TupleSplitter):

No alt text provided for this image

Como puedes observer, la estructura es similar a la de TupleSplitter, con clases contenedora (wrapper class) e impl con especialización de plantilla de clase. Las diferencias son las siguientes: 

  • Esta vez solo describimos un tipo de salida (output type) (ya que no dividimos la tupla sino que creamos una nueva), llamado type.
  • La condición dentro de std::conditional es la misma que antes, pero los tipos de resultado verdadero y falso en tiempo de compilación son diferentes. Cuando la condición es verdadera, para el tipo T, se devuelve el tipo const std::vector<T>&, y cuando es evaluada como falsa, solo devueve const T &.
  • Ya no necesitamos std::tuple_cat, porque siempre devolvemos algo: const T& or const std::vector<T>& (en el ejemplo anterior tuvimos un caso en el que necesitábamos el tipo 'no', por lo que usamos una tupla vacía y tuvimos que concatenarla al final. Aquí, en cambio, usamos std::tuple directamente con la expansión de plantilla variadic de ... (línea 29). 
  • Como he mencionado anteriormente, esta es la versión que usaré en la solución presentada en la parte IV, he añadido atributos de referencia const al tipo del resultado. Esto será muy importante cuando examinemos el rendimiento de la solución más adelante.

El uso de la clase contenedora (wrapper class) es el mismo que antes, por lo que puedes analizarlo más detalladamente por tu cuenta (líneas 32-39). Finalmente, echemos un vistazo a un ejemplo del uso de esta clase:

No alt text provided for this image

Este es un ejemplo 'completo', ya que verifica todas las máscaras posibles para una tupla de entrada de longitud 3. Veamos un valor de máscara: 011. En ese caso, la tupla de entrada completa se declara como baseTuple = std::tuple <string, float, double>, por lo que el uso de FunctorInputSetterWrapper con esta entrada y la máscara 0b011 hará que el tipo de retorno transforme solo la primera y segunda tpi de la tupla en vectores. Esto dará como resultado la siguiente tupla: tuple<const std::vector<std::string> &, const std::vector<float>&, const double&>, como se muestra en la aserción estática en las líneas 44 - 46.

Nota: en el último ejemplo, usar una referencia para duplicar me parece innecesario (hasta donde yo sé, nadie usa una referencia a tipos numéricos simples en C ++). Sin embargo, aunque esta clase TMP se usa con otro tipo, como un string o cualquier otro tipo definido por el usuario, esta referencia evita la copia de datos. Para ser uniforme con todas las posibles entradas de función (tipos simples frente a tipos complejos) y todas las máscaras posibles (dejando algunos tipos como están, convirtiendo otros en std::vector), tenemos que usar esta referencia const para todos los tipos. Puede haber una forma de evitar esto para tipos simples, no vectoriales, pero seguramente complicaríamos la solución aún más, y no es el propósito de este artículo.

Eso es todo para esta parte. Creo que hay mucho que procesar y entender aquí. Me alegro de haber incluido esta parte de manera independiente para que los lectores podáis procesarla y entenderla adecuadamente antes de tratar con el código en la siguiente parte. (He hecho un esfuerzo consciente para profundizar en los detalles tanto como sea posible, pero este no es un libro sobre plantillas o sobre TMP :)). FunctorInputSetter se utilizará como bloque de construcción en la siguiente parte, así como de las técnicas presentadas anteriormente.

Traducido por: Mireia Alba Kesti Izquierdo


Omer Pinto

Experienced Senior Software Engineer with a demonstrated history of working in the computer software industry. Skilled in Parsing, Compiler Optimization, Databases, and Big Data Analytics. Strong engineering professional with a Bachelor of Science (BSc.) focused in Mathematics and Computer Science from The Open University of Israel.
Enthusiastic about algorithm & data structures design, multi-threading systems and challenges, and modern C++ advanced templates metaprogramming techniques.

Related Posts

Boletin informativo SpainClouds.com

Thank you! Your submission has been received!

Oops! Something went wrong while submitting the form