stringtranslate.com

El fallo de sustitución no es un error

La falla de sustitución no es un error ( SFINAE ) es un principio en C++ donde una sustitución no válida de parámetros de plantilla no es en sí misma un error. David Vandevoorde introdujo por primera vez el acrónimo SFINAE para describir técnicas de programación relacionadas. [1]

Específicamente, al crear un conjunto de candidatos para la resolución de sobrecarga , algunos (o todos) los candidatos de ese conjunto pueden ser el resultado de plantillas instanciadas con argumentos de plantilla (potencialmente deducidos) sustituidos por los parámetros de plantilla correspondientes. Si se produce un error durante la sustitución de un conjunto de argumentos para cualquier plantilla determinada, el compilador elimina la sobrecarga potencial del conjunto candidato en lugar de detenerse con un error de compilación, siempre que el error de sustitución sea uno que el estándar C++ conceda dicho tratamiento. [2] Si uno o más candidatos permanecen y la resolución de la sobrecarga tiene éxito, la invocación está bien formada.

Ejemplo

El siguiente ejemplo ilustra una instancia básica de SFINAE:

prueba de estructura { typedef int foo ; };     plantilla < nombretipo T > void f ( nombretipo T :: foo ) {} // Definición #1      plantilla < nombre de tipo T > void f ( T ) {} // Definición #2     int principal ( ) { f <Prueba> ( 10 ) ; // Llamada #1. f < int > ( 10 ); // Llamada #2. Sin error (aunque no hay int::foo) // gracias a SFINAE. devolver 0 ; }         

Aquí, intentar utilizar un tipo que no sea de clase en un nombre calificado ( T::foo) da como resultado un error de deducción porque f<int>no inttiene un tipo anidado llamado foo, pero el programa está bien formado porque permanece una función válida en el conjunto de funciones candidatas.

Aunque SFINAE se introdujo inicialmente para evitar la creación de programas mal formados cuando eran visibles declaraciones de plantillas no relacionadas (por ejemplo, mediante la inclusión de un archivo de encabezado), muchos desarrolladores descubrieron más tarde que el comportamiento era útil para la introspección en tiempo de compilación. Específicamente, permite que una plantilla determine ciertas propiedades de sus argumentos de plantilla en el momento de la creación de instancias.

Por ejemplo, SFINAE se puede utilizar para determinar si un tipo contiene una determinada definición de tipo:

#incluir <iostream> template < typename T > struct has_typedef_foobar { // Se garantiza que los tipos "sí" y "no" tendrán diferentes tamaños, // específicamente sizeof(yes) == 1 y sizeof(no) == 2. typedef char yes [ 1 ] ; typedef char no [ 2 ];             plantilla < nombre de tipo C > estático y prueba ( nombre de tipo C :: foobar * );       plantilla < nombre de tipo > número estático y prueba (...);     // Si el "sizeof" del resultado de llamar a test<T>(nullptr) es igual a // sizeof(yes), la primera sobrecarga funcionó y T tiene un tipo anidado llamado // foobar. valor bool constante estático = tamaño de ( prueba <T> ( nullptr ) ) == tamaño de ( ) ; };          estructura foo { typedef float foobar ; };     int principal () { std :: cout << std :: boolalpha ; std :: cout << has_typedef_foobar < int >:: valor << std :: endl ; // Imprime false std :: cout << has_typedef_foobar < foo >:: valor << std :: endl ; // Imprime verdadero retorno 0 ; }                   

Cuando se define Tel tipo anidado foobar, la instanciación de las primeras testobras y la constante de puntero nulo se pasan con éxito. (Y el tipo resultante de la expresión es yes.) Si no funciona, la única función disponible es la segunda testy el tipo resultante de la expresión es no. Se utilizan puntos suspensivos no sólo porque aceptará cualquier argumento, sino también porque su rango de conversión es el más bajo, por lo que se preferirá una llamada a la primera función si es posible; esto elimina la ambigüedad.

Simplificación de C++11

En C++11 , el código anterior podría simplificarse a:

#incluir <iostream> #incluir <tipo_traits>  plantilla < nombretipo ... Ts > usando void_t = void ;     plantilla < typename T , typename = void > struct has_typedef_foobar : std :: false_type {};         plantilla < nombre de tipo T > estructura has_typedef_foobar < T , void_t < nombre de tipo T :: foobar >> : std :: true_type {};        estructura foo { usando foobar = float ; };      int principal () { std :: cout << std :: boolalpha ; std :: cout << has_typedef_foobar < int >:: valor << std :: endl ; std :: cout << has_typedef_foobar < foo >:: valor << std :: endl ; devolver 0 ; }                 

Con la estandarización del lenguaje de detección en la propuesta Library fundamental v2 (n4562), el código anterior podría reescribirse de la siguiente manera:

#incluir <iostream> #incluir <tipo_traits>  plantilla < nombretipo T > usando has_typedef_foobar_t = nombretipo T :: foobar ;      estructura foo { usando foobar = float ; };      int principal () { std :: cout << std :: boolalpha ; std :: cout << std :: is_detected < has_typedef_foobar_t , int >:: valor << std :: endl ; std :: cout << std :: is_detected < has_typedef_foobar_t , foo >:: valor << std :: endl ; devolver 0 ; }                   

Los desarrolladores de Boost utilizaron SFINAE en boost::enable_if [3] y de otras formas.

Referencias

  1. ^ Vandevoorde, David; Nicolai M. Josuttis (2002). Plantillas C++: la guía completa . Profesional de Addison-Wesley. ISBN 0-201-73484-2.
  2. ^ Organización Internacional de Normalización. "ISO/IEC 14882:2003, Lenguajes de programación – C++", § 14.8.2.
  3. ^ Impulsar habilitar si