stringtranslate.com

Ejecución de funciones en tiempo de compilación

En informática , la ejecución de una función en tiempo de compilación (o evaluación de una función 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 , de ejecutar la función en tiempo de compilación . Esto es posible si los argumentos de la función se conocen en tiempo de 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 sólo se conoce el valor de algunos de los argumentos, el compilador aún puede 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 conociera ningún argumento.

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 para C++ (Vandevoorde 2003) [1] fue un sistema experimental temprano 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 utilizaba a menudo para calcular valores en tiempo de compilación, como:

plantilla < int N > struct Factorial { enumeración { valor = N * Factorial < N - 1 >:: valor }; };              plantilla <> struct 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 }            

Usando la evaluación de funciones en tiempo de compilación, el código usado para calcular el factorial sería similar a lo que uno escribiría para la evaluación en tiempo de ejecución, por ejemplo, usando C++11 constexpr.

#incluir <cstdio> constexpr int Factorial ( int n ) { devolver n ? ( n * Factorial ( n - 1 )) : 1 ; }               constexpr int f10 = Factorial ( 10 );    int main () { 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 sobre constexpr, lo que permite 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 función en tiempo de compilación en C++14:

// Factorial iterativo en tiempo de compilación. constexpr int Factorial ( int n ) { int resultado = 1 ; while ( n > 1 ) { resultado *= n -- ; } return resultado ; }                   int main () { 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 ; while ( n > 1 ) { resultado *= n -- ; } return resultado ; }                   int principal () { int f4 = Factorial ( 4 ); // f4 == 24 }       

Dado que la función Factorialestá marcada consteval, se garantiza que se invoque en tiempo de compilación sin que se la fuerce en otro contexto de evaluación constante manifiesta. Por lo tanto, el uso de funciones inmediatas ofrece amplios usos en metaprogramación y verificación en tiempo de compilación (usada en la biblioteca de formato de texto de 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:

void ves este error porque la aserción falla () {}  consteval void cassert ( bool b ) { if ( ! b ) ves este error porque la aserción falla (); }       consteval void test () { int x = 10 ; cassert ( x == 10 ); // ok x ++ ; cassert ( x == 11 ); // ok 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 usar en expresiones constantes. En otras palabras, la compilación se detiene después de una aserción fallida.

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

En la función ' int main () ' : en la expansión ' constexpr ' de ' test () ' en la expansión ' constexpr ' de ' cassert ( x == 12 ) ' error : llamada a una función que no es ' constexpr ' ' usted_ve_este_error_porque_la_aserción_falla () ' usted_ve_este_error_porque_la_aserción_falla () ; ~ ...                         

A continuación se muestra otro ejemplo de uso de funciones inmediatas como constructores que permite la verificación de argumentos en tiempo de compilación:

#include <vista_de_cadena> #include <iostream>  void ves este error porque el mensaje termina con un signo de exclamación () {}  estructura checked_message { std :: string_view msg ;     consteval checked_message ( const char * arg ) : msg ( arg ) { if ( msg.ends_with ( '!' )) ves este error porque el mensaje termina con un signo de exclamación ( ) ; } };          void enviar_mensaje_tranquilo ( mensaje_comprobado arg ) { std :: cout << arg.msg << ' \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 la expansión ' constexpr ' de ' checked_message ((( const char * ) "¡Hola, mundo!" ) ) ' error : llamada a una función que no es ' constexpr ' ' void ves_este_error_porque_el_mensaje_termina_con_un_signo_de_exclamación () ' ves_este_error_porque_el_mensaje_termina_con_un_signo_de_exclamación ( ) ; ~ ...                    

D

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

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

Este ejemplo especifica una función D válida denominada "factorial" que normalmente se evaluaría en tiempo de ejecución. El uso de enumindica al compilador que el inicializador de las variables debe calcularse en tiempo de 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 rellenar estructuras de datos en tiempo de compilación de una manera sencilla (versión D 2):

int [] genFactorials ( int n ) { auto resultado = new int [ n ]; resultado [ 0 ] = 1 ; foreach ( i ; 1 .. n ) resultado [ i ] = resultado [ i - 1 ] * i ; devolver resultado ; }                         enumeración factoriales = genFactorials ( 13 );   vacío principal () {}  // 'factorials' 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 función en tiempo de compilación en el lenguaje de programación Zig : [5]

pub fn factorial ( n : usize ) usize { var resultado = 1 ; para ( 1 ..( n + 1 )) | i | { resultado *= i ; } devolver resultado ; }                     pub fn main () void { constante x = comptime factorial ( 0 ); // == 0 constante y = comptime factorial ( 4 ); // == 24 }                

Este ejemplo especifica una función Zig válida denominada "factorial" que normalmente se evaluaría en tiempo de ejecución. El uso de comptimeindica al compilador que el inicializador de las variables debe calcularse en tiempo de 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 de tiempo de compilación. [6]

pub fn factorial ( comptime n : usize ) usize { var resultado : usize = 1 ; for ( 1 ..( n + 1 )) | i | { resultado *= i ; } return resultado ; }                       pub fn main () void { const x = factorial ( 0 ); // == 0 const y = factorial ( 4 ); // == 24 }              

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

fn List ( comptime T : tipo ) tipo { return struct { elementos : [] T , len : usize , }; }             // La estructura de datos de lista genérica se puede instanciar pasando un tipo: var buffer : [ 10 ] i32 = undefined ; var list = List ( i32 ){ . items = & 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 de tiempo de compilación

Enlaces externos