El fallo de sustitución no es un error ( SFINAE ) se refiere a una situación en C ++ en la que una sustitución no válida de los parámetros de la 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 tal tratamiento. [2] Si quedan uno o más candidatos 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:
struct Test { typedef int foo ; };template < typename T > void f ( typename T :: foo ) {} // Definición n. ° 1template < typename T > void f ( T ) {} // Definición n. ° 2int main () { f < Prueba > ( 10 ); // Llamar # 1. f < int > ( 10 ); // Llamar # 2. Sin error (aunque no hay int :: foo) // gracias a SFINAE. }
Aquí, intentar usar un tipo que no es de clase en un nombre calificado ( T::foo
) da como resultado un error de deducción f
porque int
no tiene un tipo anidado nombrado foo
, pero el programa está bien formado porque una función válida permanece 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 plantilla no relacionadas (por ejemplo, mediante la inclusión de un archivo de encabezado), muchos desarrolladores encontraron más tarde el comportamiento ú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 instanciación.
Por ejemplo, SFINAE se puede utilizar para determinar si un tipo contiene un determinado typedef:
#include template < typename T > struct has_typedef_foobar { // Se garantiza que los tipos "yes" y "no" tienen diferentes tamaños, // específicamente sizeof (yes) == 1 y sizeof (no) == 2. typedef char yes [ 1 ] ; typedef char no [ 2 ]; plantilla < typename C > static yes & test ( typename C :: foobar * ); plantilla < typename > static no & test (...); // Si el "sizeof" del resultado de llamar a test (nullptr) es igual a // sizeof (sí), la primera sobrecarga funcionó y T tiene un tipo anidado llamado // foobar. valor bool const estático = tamaño de ( prueba < T > ( nullptr )) == tamaño de ( sí ); }; struct foo { typedef float foobar ; };int principal () { std :: cout << std :: boolalpha ; std :: cout << has_typedef_foobar < int > :: value << std :: endl ; // Imprime falso std :: cout << has_typedef_foobar < foo > :: value << std :: endl ; // Imprime verdadero }
Cuando se T
ha foobar
definido el tipo anidado , la instanciación del primero test
funciona y la constante de puntero nulo se pasa con éxito. (Y el tipo resultante de la expresión es yes
.) Si no funciona, la única función disponible es la segunda test
y el tipo resultante de la expresión es no
. Se utiliza una elipsis no solo 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:
#include #include plantilla < typename ... Ts > usando void_t = void ;plantilla < typename T , typename = void > struct has_typedef_foobar : std :: false_type {};plantilla < typename T > struct has_typedef_foobar < T , void_t < typename T :: foobar >> : std :: true_type {};struct foo { usando foobar = float ; };int principal () { std :: cout << std :: boolalpha ; std :: cout << has_typedef_foobar < int > :: value << std :: endl ; std :: cout << has_typedef_foobar < foo > :: valor << std :: endl ; }
Con la estandarización del lenguaje de detección en la propuesta de Library fundamental v2 (n4562) , el código anterior podría reescribirse de la siguiente manera:
#include #include plantilla < typename T > usando has_typedef_foobar_t = typename T :: foobar ;struct foo { usando foobar = float ; };int principal () { std :: cout << std :: boolalpha ; std :: cout << std :: is_detected < has_typedef_foobar_t , int > :: value << std :: endl ; std :: cout << std :: is_detected < has_typedef_foobar_t , foo > :: value << std :: endl ; }
Los desarrolladores de Boost usaron SFINAE en boost :: enable_if [3] y de otras formas.
Referencias
- ^ Vandevoorde, David; Nicolai M. Josuttis (2002). Plantillas C ++: la guía completa . Addison-Wesley Professional. ISBN 0-201-73484-2.
- ^ Organización internacional de normalización. "ISO / IEC 14882: 2003, Lenguajes de programación - C ++", § 14.8.2.
- ^ Impulsar Habilitar si