El principio de que una sustitución inválida no es un error ( SFINAE ) es un principio en C++ según el cual una sustitución inválida de parámetros de plantilla no constituye un error en sí misma. 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 ocurre un error durante la sustitución de un conjunto de argumentos para una plantilla dada, el compilador elimina la sobrecarga potencial del conjunto de candidatos en lugar de detenerse con un error de compilación, siempre que el estándar C++ permita descartar dicho error de sustitución como se mencionó. [ 2 ] Si uno o más candidatos permanecen y la resolución de sobrecarga tiene éxito, la invocación está bien formada.
SFINAE ha sido en gran medida reemplazado por la incorporación de conceptos en C++20 , que permiten expresar de forma más clara las restricciones de tipo de plantilla.
Ejemplo
El siguiente ejemplo ilustra un caso básico de SFINAE:
struct Test { using Foo = int ; };// Definición #1 plantilla < typename T > void f ( typename T :: Foo ) { // ... }// Definición #2 plantilla < typename T > void f ( T ) { // ... }int main () { f <Test> ( 10 ); // Llamada #1. f<int> ( 10 ) ; // Llamada # 2. Sin error ( aunque no exista int::Foo) debido a SFINAE . return 0 ; }Aquí, intentar usar un tipo que no sea de clase en un nombre calificado ( T::Foo) resulta en un fallo de deducción para f<int>porque intno tiene ningún tipo anidado llamado Foo, pero el programa está bien formado porque queda 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 se mostraban declaraciones de plantillas no relacionadas (por ejemplo, mediante la inclusión de un archivo de cabecera), muchos desarrolladores posteriormente encontraron útil este comportamiento para la introspección en tiempo de compilación. En concreto, permite que una plantilla determine ciertas propiedades de sus argumentos en el momento de la instanciación.
Por ejemplo, SFINAE se puede utilizar para determinar si un tipo contiene un typedef determinado:
importar std ;template < typename T > struct HasTypedefFoobar { // Se garantiza que los tipos "Sí" y "No" tendrán tamaños diferentes, // específicamente sizeof(Sí) == 1 y sizeof(No) == 2. using Sí = char [ 1 ]; using No = char [ 2 ];plantilla < typename C > static Yes & test ( typename C :: Foobar * );plantilla < typename > static No & test (...);// 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. static constexpr bool value = ( sizeof ( test < T > ( nullptr )) == sizeof ( Yes )); };struct Foo { using Foobar = float ; };int main () { std :: println ( " { }" , HasTypedefFoobar <int> :: value ); std :: println ( "{}" , HasTypedefFoobar <Foo> :: value ) ; return 0 ; }Cuando Tse ha definido el tipo anidado Foobar, la instanciación de la primera testfunciona y la constante de puntero nulo se pasa correctamente. (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 acepta 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:
importar std ;plantilla < typename T , typename = void > struct HasTypedefFoobar : std :: false_type {};plantilla < typename T > struct HasTypedefFoobar < T , std :: void_t < typename T :: Foobar >> : std :: true_type {};struct Foo { using Foobar = float ; };int main () { std :: println ( " { }" , HasTypedefFoobar <int> :: value ); std :: println ( "{}" , HasTypedefFoobar <Foo> :: value ) ; return 0 ; }Con la estandarización del patrón de detección en la propuesta Library fundamental v2 (n4562) , el código anterior podría reescribirse de la siguiente manera:
importar std ;plantilla < typename T > usando HasTypedefFoobarUnderlying = typename T :: Foobar ;struct Foo { using Foobar = float ; };int main () { std :: println ( "{}" , std :: is_detected < HasTypedefFoobarUnderlying , int >:: value ); std :: println ( "{}" , std :: is_detected < HasTypedefFoobarUnderlying , Foo >:: value ); return 0 ; }Los desarrolladores de Boost utilizaron SFINAE en boost::enable_if [ 3 ] y de otras maneras.
Referencias
- C++
- patrones de diseño de software