stringtranslate.com

Ejecución de funciones en tiempo de compilación

En informática , la ejecución de funciones en tiempo de compilación (o evaluación de funciones en tiempo de compilación , o expresiones constantes generales ) es la capacidad de un compilador , que normalmente compilaría una función en código de máquina y la ejecutaría en tiempo de ejecución , para ejecutar la función en tiempo de compilación. . Esto es posible si los argumentos de la función se conocen en el momento de la compilación y la función no hace ninguna referencia ni intenta modificar ningún estado global (es decir, es una función pura ).

Si se conoce el valor de solo algunos de los argumentos, es posible que el compilador aún pueda realizar algún nivel de ejecución de funciones en tiempo de compilación ( evaluación parcial ), posiblemente produciendo código más optimizado que si no se conocieran los argumentos.

Ejemplos

Ceceo

El sistema de macros Lisp es un ejemplo temprano del uso de la evaluación en tiempo de compilación de funciones definidas por el usuario en el mismo lenguaje.

C++

La extensión Metacode a C++ (Vandevoorde 2003) [1] fue uno de los primeros sistemas experimentales que permitía la evaluación de funciones en tiempo de compilación (CTFE) y la inyección de código como una sintaxis mejorada para la metaprogramación de plantillas de C++ .

En versiones anteriores de C++ , la metaprogramación de plantillas se utiliza a menudo para calcular valores en tiempo de compilación, como por ejemplo:

plantilla < int N > estructura Factorial { enumeración { valor = N * Factorial < N - 1 >:: valor }; };              plantilla <> estructura Factorial < 0 > { enumeración { valor = 1 }; };         // Factorial<4>::valor == 24 // Factorial<0>::valor == 1 void Foo () { int x = Factorial < 0 >:: valor ; // == 1 int y = Factorial < 4 >:: valor ; // == 24 }            

Al utilizar la evaluación de funciones en tiempo de compilación, el código utilizado para calcular el factorial sería similar a lo que se escribiría para la evaluación en tiempo de ejecución, por ejemplo, utilizando C++11 constexpr.

#incluir <cstdio> constexpr int Factorial ( int n ) { retorno n ? ( n * Factorial ( n - 1 )) : 1 ; }               constexpr int f10 = Factorial ( 10 );    int principal () { printf ( "%d \n " , f10 ); devolver 0 ; }      

En C++11 , esta técnica se conoce como expresiones constantes generalizadas ( constexpr). [2] C++14 relaja las restricciones de constexpr, permitiendo declaraciones locales y el uso de condicionales y bucles (se mantiene la restricción general de que todos los datos necesarios para la ejecución estén disponibles en tiempo de compilación).

A continuación se muestra un ejemplo de evaluación de funciones en tiempo de compilación en C++14:

// Factorial iterativo en tiempo de compilación. constexpr int Factorial ( int n ) { int resultado = 1 ; mientras ( n > 1 ) { resultado *= n -- ; } devolver resultado ; }                   int principal () { constexpr int f4 = Factorial ( 4 ); // f4 == 24 }        

Funciones inmediatas (C++)

En C++20 , se introdujeron funciones inmediatas y la ejecución de funciones en tiempo de compilación se hizo más accesible y flexible con constexprrestricciones relajadas.

// Factorial iterativo en tiempo de compilación. consteval int Factorial ( int n ) { int resultado = 1 ; mientras ( n > 1 ) { resultado *= n -- ; } devolver resultado ; }                   int principal () { int f4 = Factorial ( 4 ); // f4 == 24 }       

Dado que la función Factorialestá marcada consteval, se garantiza que se invocará en tiempo de compilación sin ser forzada en otro contexto manifiestamente evaluado de manera constante. Por lo tanto, el uso de funciones inmediatas ofrece amplios usos en metaprogramación y verificación en tiempo de compilación (utilizadas en la biblioteca de formato de texto C++20).

A continuación se muestra un ejemplo del uso de funciones inmediatas en la ejecución de funciones en tiempo de compilación:

anular you_see_this_error_porque_assertion_fails () {}  consteval void cassert ( bool b ) { if ( ! b ) you_see_this_error_porque_assertion_fails (); }       prueba de vacío consteval () { int x = 10 ; cassert ( x == 10 ); // está bien x ++ ; cassert ( x == 11 ); // vale x -- ; cassert ( x == 12 ); // falla aquí }                     int principal () { prueba (); }    

En este ejemplo, la compilación falla porque la función inmediata invocó una función que no se puede utilizar en expresiones constantes. En otras palabras, la compilación se detiene después de una afirmación fallida.

El típico mensaje de error de compilación mostraría:

En la función ' int main () ' : en ' constexpr ' expansión de ' test () ' en ' constexpr ' expansión de ' cassert ( x == 12 ) ' error : llamada a función no ' constexpr ' ' you_see_this_error_porque_assertion_fails ( ) ' you_see_this_error_porque_assertion_fails (); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~ [ ... ]                         

Aquí hay otro ejemplo del uso de funciones inmediatas como constructores que permite la verificación de argumentos en tiempo de compilación:

#incluir <vista_cadena> #incluir <iostream>  anular you_see_this_error_porque_the_message_ends_with_exclamation_point () {}  estructura mensaje_comprobado { std :: string_view msg ;     consteval mensaje_comprobado ( const char * arg ) : mensaje ( arg ) { if ( msg . termina_con ( '!' )) usted_ve_este_error_porque_el_mensaje_termina_con_punto_de_exclamación (); } };          void send_calm_message ( check_message arg ) { std :: cout << arg . mensaje << '\n' ; }        int main () { send_calm_message ( "Hola mundo" ); send_calm_message ( "¡Hola mundo!" ); }    

La compilación falla aquí con el mensaje:

En la función ' int main () ' : en ' constexpr ' expansión de ' check_message ((( const char * ) "¡Hola, mundo!" )) ' error : llamada a una función que no es ' constexpr ' ' void you_see_this_error_porque_el_mensaje_termina_con_punto_de_exclamación ( ) ' you_see_this_error_porque_el_mensaje_termina_con_punto_de_exclamación (); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~^~ [ ... ]                    

D

A continuación se muestra un ejemplo de evaluación de funciones en tiempo de compilación en el lenguaje de programación D : [3]

int factorial ( int n ) { si ( n == 0 ) devuelve 1 ; devolver n * factorial ( n - 1 ); }               // calculado en tiempo de compilación enum y = factorial ( 0 ); // == 1 enumeración x = factorial ( 4 ); // == 24        

Este ejemplo especifica una función D válida llamada "factorial" que normalmente se evaluaría en tiempo de ejecución. El uso de enumle dice al compilador que el inicializador de las variables debe calcularse en el momento de la compilación. Tenga en cuenta que los argumentos de la función también deben poder resolverse en tiempo de compilación. [4]

CTFE se puede utilizar para completar estructuras de datos en tiempo de compilación de una manera sencilla (D versión 2):

int [] genFactorials ( int n ) { resultado automático = nuevo int [ n ]; resultado [ 0 ] = 1 ; foreach ( i ; 1 .. n ) resultado [ i ] = resultado [ i - 1 ] * i ; resultado de retorno ; }                         enumeración factoriales = genFactoriales ( 13 );   vacío principal () {}  // 'factoriales' contiene en tiempo de compilación: // [1, 1, 2, 6, 24, 120, 720, 5_040, 40_320, 362_880, 3_628_800, // 39_916_800, 479_001_600]

CTFE se puede utilizar para generar cadenas que luego se analizan y compilan como código D en D.

Zig

A continuación se muestra un ejemplo de evaluación de funciones en tiempo de compilación en el lenguaje de programación Zig : [5]

pub fn factorial ( n : usar tamaño ) usar tamaño { var resultado = 1 ; para ( 1 ..( norte + 1 )) | yo | { resultado *= yo ; } devolver resultado ; }                     pub fn main () void { const x = comptime factorial ( 0 ); // == 0 const y = comptime factorial ( 4 ); // == 24 }                

Este ejemplo especifica una función Zig válida llamada "factorial" que normalmente se evaluaría en tiempo de ejecución. El uso de comptimele dice al compilador que el inicializador de las variables debe calcularse en el momento de la compilación. Tenga en cuenta que los argumentos de la función también deben poder resolverse en tiempo de compilación.

Zig también admite parámetros en tiempo de compilación. [6]

pub fn factorial ( comptime n : usar tamaño ) usar tamaño { var resultado : usar tamaño = 1 ; para ( 1 ..( norte + 1 )) | yo | { resultado *= yo ; } devolver resultado ; }                       pub fn principal () void { const x = factorial ( 0 ); // == 0 constante y = factorial ( 4 ); // == 24 }              

CTFE se puede utilizar para crear estructuras de datos genéricas en tiempo de compilación:

fn Lista ( comptime T : tipo ) tipo { return struct { elementos : [] T , len : usar tamaño , }; }             // Se puede crear una instancia de la estructura de datos de Lista genérica pasando un tipo: var buffer : [ 10 ] i32 = undefinido ; var lista = Lista ( i32 ){ . elementos = & buffer , . len = 0 , };             

Referencias

  1. ^ Daveed Vandevoorde, Edison Design Group (18 de abril de 2003). "Metaprogramación reflexiva en C++" (PDF) . Consultado el 19 de julio de 2015 .
  2. ^ Gabriel Dos Reis y Bjarne Stroustrup (marzo de 2010). "Expresiones constantes generales para lenguajes de programación de sistemas. SAC-2010. 25º Simposio ACM sobre informática aplicada" (PDF) .
  3. ^ Especificación del lenguaje D 2.0: funciones
  4. ^ Especificación del lenguaje D 2.0: atributos
  5. ^ Referencia del lenguaje Zig 0.11.0: expresiones en tiempo de compilación
  6. ^ Referencia del lenguaje Zig 0.11.0: parámetros en tiempo de compilación

enlaces externos