SFINAE

SFINAE ( English  substitution failure is not an error , “failed substitution is not an error”) es una regla del lenguaje C++ asociada a plantillas y sobrecarga de funciones . Se usa ampliamente "para otros fines", para reflexionar durante la compilación : dependiendo de las propiedades del tipo, la compilación va de una forma u otra.

La regla SFINAE dice: Si los tipos/valores finales de los parámetros de plantilla de una función no se pueden calcular, el compilador no arroja un error, sino que busca otra sobrecarga adecuada. El error será en tres casos:

Historia

La regla existía en C++98 y se inventó para que el programa no generara errores si en algún lugar de los archivos de encabezado había una plantilla con el mismo nombre, lejos del contexto. Pero luego resultó ser conveniente para la reflexión durante la compilación. El acrónimo SFINAE fue acuñado por David Vandervoord, autor de C++ Patterns (2002).

Se ha agregado una plantilla simple a Boost que opera con la regla SFINAE y le permite crear instancias de una plantilla bajo ciertas condiciones. enable_if

En el estándar C++11 , la regla SFINAE se ha refinado un poco sin cambiar el concepto. La plantilla también entró allí (en general , , , y muchas más fueron prestadas de Boost ).enable_ifchronorandomfilesystem

En C++17 , se agregó una construcción que redujo ligeramente la necesidad de SFINAE. if constexpr()

C ++ 20 introdujo el . Por un lado, la constante entre paréntesis también forma parte de la sustitución, y si no se calcula, será una sustitución fallida. Por otro lado, también reduce la necesidad de SFINAE. También redujo la necesidad del concepto SFINAE . explicit (true)

Cita original

Supongamos que necesitamos llamar a una función

f ( 1 , 2 );

Hay versiones de esta función:

( 1 ) vacío f ( int , std :: vector < int > ); ( 2 ) vacío f ( int , int ); ( 3 ) vacío f ( doble , doble ); ( 4 ) void f ( int , int , char , estándar :: cadena , estándar :: vector < int > ); ( 5 ) vacío f ( estándar :: cadena ); ( 6 ) vacío f (...);

El compilador reúne estas funciones en una lista y encuentra la mejor según ciertas reglas: produce una resolución de sobrecarga . 

  1. Primero, el compilador descarta las funciones que no coinciden con la cantidad de parámetros: 4 y 5.
  2. Luego, las sustituciones de plantillas se descartan, donde no fue posible calcular los tipos de parámetros de entrada y regresar, no hay ninguno.
  3. Luego, la función 1 se descarta; no hay una conversión de tipo adecuada para ella.
  4. Y de 2, 3 y 6, de acuerdo con reglas bastante complicadas, el compilador elige 2: ambos tipos son exactamente iguales. Si no hubiera un ganador absoluto, el compilador arrojaría un error indicando entre qué opciones fluctúa.

El paso 2, relacionado con las funciones de la plantilla, aún no se ha activado. Agreguemos dos funciones más a nuestra lista.

( 7 ) plantilla < nombre de tipo T > vacío f ( T , T ); ( 8 ) plantilla < nombre de tipo T > void f ( T , nombre de tipo T :: iterador );

La función 7 se descartará en el cuarto paso, porque una función sin plantilla siempre es "más fuerte" que una con plantilla.

La plantilla 8 está lejos de ser nuestra tarea, ya que está diseñada para una determinada clase que tiene tipo iterator. El segundo paso es SFINAE : el compilador dice que T = intintenta sustituir inten la plantilla, y aquellas plantillas donde la sustitución no condujo al éxito son descartadas. Por lo tanto, una sustitución fallida no es un error .

Un ejemplo de reflexión al compilar con SFINAE

Este ejemplo compila incluso en C++03 .

#incluir <iostream> #incluir <vector> #incluir <conjunto> plantilla < typenameT > _ clase DetectarBuscar { estructura alternativa { int encontrar ; }; // agregar el nombre del miembro estructura "buscar" Derivado : T , Fallback { }; template < typename U , U > struct Check ; typedef char [ 1 ]; // typedef para una matriz de tamaño uno. typedef char No [ 2 ]; // typedef para una matriz de tamaño dos. plantilla < nombre de tipo U > static No & func ( Check < int Fallback ::* , & U :: find > * ); plantilla < nombre de tipo U > estático & función (...); público : typedef DetectarBuscar tipo ; enum { valor = tamaño de ( func < Derivado > ( 0 )) == tamaño de ( ) }; }; int principal () { std :: cout << DetectFind < std :: vector < int >> :: valor << ' ' << DetectFind < std :: set < int > >:: value << std :: endl ; devolver 0 ; }

Cómo funciona: la resolución de sobrecarga ocurre en una cadena y el tipo concreto es más fuerte que los argumentos variables . Debido al hecho de que en , no hay necesidad de instanciar funciones de plantilla, es suficiente sustituir tipos; por lo tanto, las funciones solo tienen encabezados sin cuerpos. La segunda función, que devuelve tipo , siempre será sustituida, pero ¿qué pasa con la primera? sizeof(func<Derived>(0))Check<int Fallback::*, &U::find> *...funcsizeofYes

Se sustituirá si existirá el tipo de plantilla (ya que debajo del puntero, el tipo exacto no es importante, lo principal es la existencia). El primer parámetro de plantilla es un tipo, el segundo es una constante de ese tipo. El puntero al campo del objeto se toma como tipo (de hecho, el desplazamiento desde el principio del objeto al campo), como una constante, el puntero al campo . La constante se definirá y será del tipo correcto si el único campo se toma del objeto  , es decir, no hay otro tomado prestado de . CheckCheckintFallbackfindDerived::findFallbackfindT

Notas

Enlaces

  • SFINAE  (inglés) . cppreference.com. Consultado el 9 de enero de 2020. Archivado desde el original el 6 de mayo de 2021.
En ruso