c++ c++11 templates c++14 typetraits

c++ - ¿Cómo funciona la implementación de std:: is_function de Eric Niebler?



c++11 templates (1)

La idea general

En lugar de enumerar todos los tipos de funciones válidos, como la std::is_function , esta implementación enumera todos los tipos que no son funciones y luego solo se resuelve como true si ninguno de ellos coincide.

La lista de tipos sin función consiste en (de abajo hacia arriba):

  • Clases y uniones (incluidos los tipos abstractos)
  • Cualquier cosa que pueda devolverse desde una función (incluidos los tipos de referencia y void )
  • Tipos de matriz

Un tipo que no coincide con ninguno de esos tipos sin función es un tipo de función. Tenga en cuenta que std::is_function considera explícitamente que los tipos invocables como lambdas o clases con un operador de llamada a función no son funciones.

is_function_impl_

Proporcionamos una sobrecarga de la función is_function_impl para cada uno de los posibles tipos sin función. Las declaraciones de funciones pueden ser un poco difíciles de analizar, así que vamos a desglosarlas para el ejemplo del caso de clases y uniones :

template<typename T, typename = int T::*> char(&is_function_impl_(priority_tag<3>))[4];

Esta línea declara una plantilla de función is_function_impl_ que toma un solo argumento de tipo priority_tag<3> y devuelve una referencia a una matriz de 4 caracteres. Como es habitual desde los antiguos tiempos de C, la sintaxis de la declaración se complica terriblemente por la presencia de tipos de matriz.

Esta plantilla de función toma dos argumentos de plantilla. El primero es solo una T sin restricciones, pero el segundo es un puntero a un miembro de T de tipo int . La parte int aquí realmente no importa, es decir. esto incluso funcionará para T s que no tienen miembros de tipo int . Sin embargo, lo que hace es que dará como resultado un error de sintaxis para T que no son de tipo clase o unión. Para esos otros tipos, intentar crear una instancia de la plantilla de función resultará en una falla de sustitución.

Se utilizan trucos similares para las sobrecargas priority_tag<2> y priority_tag<1> , que utilizan sus segundos argumentos de plantilla para formar expresiones que solo compilan para T como tipos de retorno de función válidos o tipos de matriz, respectivamente. Solo la sobrecarga priority_tag<0> no tiene un segundo parámetro de plantilla tan restrictivo y, por lo tanto, puede instanciarse con cualquier T

En total, declaramos cuatro sobrecargas diferentes para is_function_impl_ , que difieren en su argumento de entrada y tipo de retorno. Cada uno de ellos toma un tipo de priority_tag diferente como argumento y devuelve una referencia a una matriz de caracteres de diferente tamaño único.

is_function etiquetas en is_function

Ahora, al crear instancias de is_function , is_function_impl instancias de is_function_impl con T Tenga en cuenta que dado que proporcionamos cuatro sobrecargas diferentes para esta función, la resolución de sobrecarga debe tener lugar aquí. Y dado que todas estas sobrecargas son plantillas de funciones, eso significa que SFINAE tiene la oportunidad de SFINAE .

Entonces, para las funciones (y solo las funciones), todas las sobrecargas fallarán, excepto la más general con priority_tag<0> . Entonces, ¿por qué la instanciación no siempre resuelve esa sobrecarga, si es la más general? Debido a los argumentos de entrada de nuestras funciones sobrecargadas.

Tenga en cuenta que priority_tag se construye de tal manera que priority_tag<N+1> hereda públicamente de priority_tag<N> . Ahora, dado que is_function_impl se invoca aquí con priority_tag<3> , esa sobrecarga es una mejor coincidencia que las otras para la resolución de sobrecarga, por lo que se intentará primero. Solo si eso falla debido a un error de sustitución, se intenta la siguiente mejor coincidencia, que es la sobrecarga priority_tag<2> . Continuamos de esta manera hasta que encontremos una sobrecarga que pueda ser instanciada o lleguemos a priority_tag<0> , que no está restringida y siempre funcionará. Dado que todos los tipos sin función están cubiertos por las sobrecargas de prio más altas, esto solo puede suceder para los tipos de función.

Evaluando el resultado

Ahora inspeccionamos el tamaño del tipo devuelto por la llamada a is_function_impl_ para evaluar el resultado. Recuerde que cada sobrecarga devuelve una referencia a una matriz de caracteres de diferente tamaño. Por lo tanto, podemos usar sizeof para verificar qué sobrecarga se seleccionó y solo establecer el resultado en true si alcanzamos la sobrecarga priority_tag<0> .

Errores conocidos

Johannes Schaub encontró un error en la implementación. Una matriz de tipo de clase incompleta se clasificará incorrectamente como una función. Esto se debe a que el mecanismo de detección actual para los tipos de matriz no funciona con tipos incompletos.

La semana pasada, Eric Niebler tweeted una implementación muy compacta para la clase de rasgos std::is_function :

#include <type_traits> template<int I> struct priority_tag : priority_tag<I - 1> {}; template<> struct priority_tag<0> {}; // Function types here: template<typename T> char(&is_function_impl_(priority_tag<0>))[1]; // Array types here: template<typename T, typename = decltype((*(T*)0)[0])> char(&is_function_impl_(priority_tag<1>))[2]; // Anything that can be returned from a function here (including // void and reference types): template<typename T, typename = T(*)()> char(&is_function_impl_(priority_tag<2>))[3]; // Classes and unions (including abstract types) here: template<typename T, typename = int T::*> char(&is_function_impl_(priority_tag<3>))[4]; template <typename T> struct is_function : std::integral_constant<bool, sizeof(is_function_impl_<T>(priority_tag<3>{})) == 1> {};

pero como funciona?