Capítulo 2 Plantillas de clase: derivación de 2.9 Argumentos de plantilla de clase

2.9 Deducción de argumento de plantilla de clase

Derivación de 2.9 parámetros reales de plantilla

 

Hasta C ++ 17, siempre tenía que pasar todos los tipos de parámetros de plantilla a las plantillas de clase (a menos que tengan valores predeterminados). Desde C ++ 17, la restricción de que siempre tiene que especificar los argumentos de la plantilla explícitamente se relajó. En su lugar, puede omitir la definición explícita de los argumentos de las plantillas, si el constructor puede deducir todos los parámetros de la plantilla (que no tienen un valor predeterminado),

Antes de C ++ 17, siempre tenía que pasar todos los argumentos de plantilla a la plantilla de clase (a menos que tuvieran valores predeterminados). A partir de C ++ 17, la restricción de especificar explícitamente argumentos de plantilla se ha relajado. Por el contrario, si el constructor puede derivar todos los parámetros de la plantilla (estos parámetros no contienen valores predeterminados), entonces se puede ignorar la definición de los parámetros reales de la plantilla,

 

Por ejemplo, en todos los ejemplos de código anteriores, puede usar un constructor de copia sin especificar los argumentos de la plantilla:

Por ejemplo, en todos los ejemplos de código anteriores, cuando se utiliza la construcción de copia, no puede especificar argumentos de plantilla:

Stack < int > intStack1; // stack of strings 
Stack < int > intStack2 = intStack1; // OK: adecuado para todas las versiones 
Stack intStack3 = intStack1; // OK: comenzar desde C ++ 17

Al proporcionar constructores que pasan algunos argumentos iniciales, puede admitir la deducción del tipo de elemento de una pila. Por ejemplo, podríamos proporcionar una pila que puede ser inicializada por un solo elemento:

Al proporcionar un constructor para pasar algunos parámetros iniciales, puede admitir la derivación de tipos de elementos de pila. Por ejemplo, podemos proporcionar una pila inicializada por un solo elemento.

template <typename T>
 class Stack {
 private : 
    std :: vector <T> elems; // element 
public : 
    Stack () = default ; 
    Stack (T const & elem) // Inicializa la pila a través de un elemento Con un elemento) 

        : elems ({elem}) { 
    } 
};

Esto le permite declarar una pila de la siguiente manera:

Esto le permite declarar una pila de la siguiente manera:

Stack intStack = 0 ; // De C ++ 17, se deriva como Stack <int>

 

Al inicializar la pila con el entero 0, se deduce que el parámetro de plantilla T es int, de modo que se instancia una pila <int>.

Al usar el entero 0 para inicializar la pila, el parámetro de plantilla T se puede derivar como int. Esto crea una pila <int>.

 

Tenga en cuenta lo siguiente:

Tenga en cuenta lo siguiente:

    • Debido a la definición del constructor int, debe solicitar que los constructores predeterminados estén disponibles con su comportamiento predeterminado, porque el constructor predeterminado solo está disponible si no se define otro constructor:

    Dado que el constructor int está definido, debe requerir que el comportamiento predeterminado del constructor predeterminado esté disponible. Porque en el caso de definir otros constructores, no se proporciona el constructor predeterminado:

Pila () = predeterminado ;

    • El argumento elem se pasa a elems con llaves alrededor para inicializar el vector elems con una lista inicializadora con elem como único argumento:

    El parámetro real elem se pasa a los elementos a través de llaves, y el vector de elementos se inicializa con una lista de inicialización con solo un elemento elem.

: elems ({elem})   // En este momento, elems tiene solo un elemento (elem).

No hay un constructor para un vector que pueda tomar un solo parámetro como elemento inicial directamente.

Vector no acepta directamente un solo parámetro como el constructor del elemento inicial.

 

Tenga en cuenta que, a diferencia de las plantillas de función, los argumentos de plantilla de clase no pueden deducirse solo parcialmente (especificando explícitamente solo algunos de los argumentos de plantilla). Vea la Sección 15.12 en la página 314 para más detalles.

Tenga en cuenta que, a diferencia de las plantillas de función, los parámetros de plantilla de clase no pueden derivarse solo parcialmente (solo algunos argumentos de plantilla se especifican explícitamente). Para más detalles, consulte la sección 15.12 en la página 314.

 

Deducción de argumentos de plantilla de clase con cadenas literales

Derivar argumentos de plantilla de clase con literales de cadena

 

En principio, incluso puede inicializar la pila con un literal de cadena:

En principio, incluso puede usar literales de cadena para inicializar la pila.

Stack stringStack = " bottom " ; // De C ++ 17, se deriva como Stack <char const [7]>

Pero esto causa muchos problemas: en general, al pasar argumentos de una plantilla tipo T por referencia, el parámetro no decae, que es el término para que el mecanismo convierta un tipo de matriz sin formato al tipo de puntero sin formato correspondiente. Esto significa que realmente inicializamos un Stack <char const [7]> y usamos type char const [7] donde sea que se use T. Por ejemplo, no podemos empujar una cadena de diferente tamaño, porque tiene un tipo diferente. Para una discusión detallada, vea la Sección 7.4 en la página 115.

Pero esto traerá muchos problemas: generalmente, cuando el parámetro real del tipo de plantilla T se pasa por referencia, el tipo del parámetro real no se degenerará (decaimiento), el término "decaimiento" aquí se refiere al "tipo de matriz original En el mecanismo de tipo de puntero original correspondiente ". (Anotación: el literal de cadena es un tipo de matriz, cuando la matriz se pasa por referencia, se deducirá al tipo de "referencia de matriz" y no se degenerará en un puntero)

 

Sin embargo, al pasar argumentos de un tipo de plantilla T por valor, el parámetro decae, que es el término para que el mecanismo convierta un tipo de matriz sin formato al tipo de puntero sin formato correspondiente. Es decir, se deduce que el parámetro de llamada T del constructor es char const *, de modo que toda la clase se deduce como Stack <char const *>.

Sin embargo, si el parámetro real del tipo de plantilla T se pasa por valor, el tipo de parámetro real se degenerará (el término "decaimiento" es como se describió anteriormente). Es decir, el parámetro de llamada T en el constructor de derivación es char const *, de modo que toda la clase es de tipo Stack <char const *>.

 

Por esta razón, puede valer la pena declarar el constructor para que el argumento pase por valor:

Por esta razón, vale la pena declarar un constructor que pasa argumentos por valor:

template <typename T>
 class Stack {
 private : 
    std :: vector <T> elems; // elementos 
public : 
    Stack (T elem) // Pase un elemento para inicializar la pila por valor 
        : elems ({elem}) { / / Para hacer la derivación de plantilla de clase (decaimiento) 
    } 
};

Con esto, la siguiente inicialización funciona bien:

De esta manera, la siguiente inicialización funciona normalmente:

Stack stringStack = " bottom " ; // De C ++ 17, se deriva como Stack <char const *>

En este caso, sin embargo, deberíamos mover mejor el elemento temporal a la pila para evitar copiarlo innecesariamente:

Sin embargo, en este caso, es mejor mover el objeto temporal elem a la pila para evitar copias innecesarias:

template <typename T>
 class Stack {
 private : std :: vector <T> elems; // elementos 
public : 
    Stack (T elem) // inicializa la pila con un elemento por valor 
        : elems ({std :: move (elem)}) { 
    } 
};

 

Guías de deducción

Asistente de derivación

 

En lugar de declarar que el constructor se llamará por valor, hay una solución diferente: dado que manejar punteros sin formato en contenedores es una fuente de problemas, debemos desactivar automáticamente la deducción de punteros de caracteres sin formato para las clases de contenedor.

Además de declarar que el constructor pasa parámetros por valor, hay una solución diferente: debido a que el manejo de los punteros sin procesar en el contenedor es la fuente del problema, debemos deshabilitar la capacidad del contenedor para derivar automáticamente los punteros de caracteres sin procesar.

Puede definir guías de deducción específicas para proporcionar deducciones adicionales de argumentos de plantilla de clase o corregirlas. Por ejemplo, puede definir que cada vez que se pasa un literal de cadena o una cadena C, la pila se instancia para std :: string:

Puede definir un "asistente de derivación" especial para proporcionar más o corregir la derivación de los parámetros de plantilla de clase existentes. Por ejemplo, puede definir que cada vez que se pasa un literal de cadena o una cadena C, se instancia como una pila std :: string.

Pila ( char  const *) -> Pila <std :: string >;

 

Esta guía debe aparecer en el mismo ámbito (espacio de nombres) que la definición de clase.

El asistente debe aparecer en el mismo ámbito (espacio de nombres) que la definición de clase.

 

Por lo general, sigue la definición de clase. Llamamos al tipo que sigue a -> el tipo guiado de la guía de deducción.

Por lo general, sigue la definición de clase. Llamamos al tipo que sigue "->" el tipo de guía "Guía de Derivación".

 

Ahora, la declaración con

Ahora, la declaración es

Pila stringStack { " bottom " }; // OK: Pila <std :: string> deducida desde C ++ 17

deduce que la pila es una pila <std :: string>.

La pila se deducirá al tipo Pila <std :: string>.

 

Sin embargo, lo siguiente aún no funciona:

Sin embargo, lo siguiente aún no es válido:

Stack stringStack = " bottom " ; // Derivado como Stack <std :: string>, pero aún no es válido.

Deducimos std :: string para crear una instancia de Stack <std :: string>:

Derivamos std :: string para crear una instancia de Stack <std :: string>

class Stack {
 privado : 
    std :: vector <std :: string > elems; // elementos 
public : 
    Stack (std :: string  const & elem) // inicializa la pila con un elemento 
        : elems ({elem}) { 
    } 
};

Sin embargo, según las reglas del lenguaje, no puede copiar initialize (initialize using =) un objeto pasando un literal de cadena a un constructor que espera un std :: string. Por lo tanto, debe inicializar la pila de la siguiente manera:

Sin embargo, de acuerdo con las reglas del lenguaje, no puede pasar literales de cadena al constructor que requiere std :: string por inicialización de copia (usando "=" inicialización). Por lo tanto, debe inicializar la pila de la siguiente manera (Anotación: la inicialización directa y la inicialización de la copia son diferentes, consulte el "experimento de programación" más adelante en el artículo):

Stack stringStack { " bottom " }; // Derivado como Stack <std :: string>, y es válido.

Tenga en cuenta que, en caso de duda, la deducción de argumentos de plantilla de clase copia. Después de declarar stringStack como Stack <std :: string>, las siguientes inicializaciones declaran el mismo tipo (por lo tanto, llamando al constructor de copias) en lugar de inicializar una pila por elementos que son pilas de cadenas:

Tenga en cuenta que si tiene dudas, use una copia de la derivación del argumento de la plantilla de clase. Después de declarar stringStack como un tipo Stack <std :: string>, las siguientes declaraciones de inicialización son todas del mismo tipo. Por lo tanto, inicialice la pila llamando al constructor de copia en lugar de los elementos de la pila.

Stack stack2 {stringStack}; // Derivado como Stack <std :: string> 
Stack stack3 (stringStack); // Derivado como Stack <std :: string> 
Stack stack4 = {stringStack}; // Derivado como <std :: string >

Consulte la Sección 15.12 en la página 313 para obtener más detalles sobre la deducción de argumentos de plantilla de clase.

Para obtener información más detallada sobre la derivación de argumentos de plantilla de clase, consulte la sección 15.12 en la página 313.

Asistente de derivación [experimento de programación]

#include <iostream> 
#include <vector>
 usando el  espacio de nombres std; 

clase Test 
{ 
public : 

    Test ( const std :: string ) {} 
}; 

template <typename T>
 class Stack {
 private : std :: vector <T> elems; // elementos 
public : 
    Stack () = default ; 

    Stack ( const T & elem) // ¡ Tenga en cuenta que aquí se puede pasar por referencia! 
        : elems ({elem}) { 
    } 
    
    // Pila (const char * elem) {} 
};

Stack ( const  char *) -> Stack <std :: string >; // Derivation Wizard 

int main () 
{ 
    / * ***** La diferencia entre inicialización directa e inicialización de copia ***** * / 
    // 1. Inicialización directa: convertirá "abcd" a cadena primero, y luego pasará Prueba (cadena)
     // 2. El requisito de conversión implícita para la inicialización de copia: de "abcd" a Prueba debe generarse directamente, no puede haber otros objetos temporales en el medio ( Tal como una cuerda). 

    Prueba t1 ( " abcd " ); // OK 
    
    // Prueba t2 = "abcd"; // ERROR, copiar los requisitos de inicialización de "abcd" a Prueba se puede convertir directamente. Es decir, no se puede convertir en una cadena antes de generar una Prueba.
                       // Para lograr la conversión directa, debe llamar a Test (const char *), pero debido a que esta clase no proporciona dicho constructor,
                        // la compilación falla. 
    

    / ****** Asistente de derivación ***** * /     
    // Stack st1 = "abcd";   // La derivación es de tipo Stack <std :: string>, pero no se puede compilar.
                           // Aquí, debido a las características del lenguaje, al copiar e inicializar (usando "=") para inicializar)
                            // el literal de cadena (const char * type) no se puede pasar al constructor de Stack (std :: string).
                           // Si agrega un constructor Stack (const char *) a Stack <String>, puede compilarlo. Principio
                            // ver la construcción del objeto t1. 

    Stack st2 ( " abcd " );   // OK, Stack (const char *) -> Stack (std :: string) 
    Stack st3 { " abcd " }; // OK, ibid. 

    // A través de la copia, llame al constructor de copias para inicializar Stack <std ::
    Stack st4 = st2; // OK, llame al constructor de copia de 
    Stack Stack st5 (st2);   // Igual que 
    Stack st6 {st2}; // Igual que 

    return  0 ; 
}

Supongo que te gusta

Origin www.cnblogs.com/5iedu/p/12709243.html
Recomendado
Clasificación