Выполнение функции во время компиляции
В вычислениях ( выполнение функции во время компиляции или оценка функции во время компиляции , или общие константные выражения ) — это способность компилятора , который обычно компилирует функцию в машинный код и выполняет ее во время выполнения , выполнять функцию во время компиляции. . Это возможно, если аргументы функции известны во время компиляции, и функция не делает никаких ссылок или не пытается изменить какое-либо глобальное состояние (т.е. это чистая функция ).
Если известны значения только некоторых аргументов, компилятор все равно сможет выполнить некоторый уровень выполнения функции во время компиляции ( частичная оценка ), возможно, создав более оптимизированный код, чем если бы аргументы не были известны.
Примеры
[ редактировать ]Лисп
[ редактировать ]Макросистема Lisp является ранним примером использования оценки во время компиляции определяемых пользователем функций на том же языке.
С++
[ редактировать ]Расширение метакода для C++ (Вандеворде, 2003). [1] была ранней экспериментальной системой, позволяющей оценивать функции во время компиляции (CTFE) и внедрять код в качестве улучшенного синтаксиса для метапрограммирования шаблонов C++ .
В более ранних версиях C++ метапрограммирование шаблонов часто использовалось для вычисления значений во время компиляции, например:
template <int N>
struct Factorial {
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0> {
enum { value = 1 };
};
// Factorial<4>::value == 24
// Factorial<0>::value == 1
void Foo() {
int x = Factorial<0>::value; // == 1
int y = Factorial<4>::value; // == 24
}
При использовании оценки функции во время компиляции код, используемый для вычисления факториала, будет аналогичен тому, который можно было бы написать для оценки во время выполнения, например, с использованием C++ 11 constexpr.
#include <cstdio>
constexpr int Factorial(int n) { return n ? (n * Factorial(n - 1)) : 1; }
constexpr int f10 = Factorial(10);
int main() {
printf("%d\n", f10);
return 0;
}
В C++11 этот метод известен как обобщенные константные выражения ( constexpr
). [2] C++14 ослабляет ограничения на constexpr — разрешая локальные объявления и использование условий и циклов (общее ограничение, заключающееся в том, что все данные, необходимые для выполнения, должны быть доступны во время компиляции, остается).
Вот пример оценки функции времени компиляции в C++14:
// Iterative factorial at compile time.
constexpr int Factorial(int n) {
int result = 1;
while (n > 1) {
result *= n--;
}
return result;
}
int main() {
constexpr int f4 = Factorial(4); // f4 == 24
}
Непосредственные функции (C++)
[ редактировать ]В C++20 были введены непосредственные функции, а выполнение функций во время компиляции стало более доступным и гибким. constexpr
ограничения.
// Iterative factorial at compile time.
consteval int Factorial(int n) {
int result = 1;
while (n > 1) {
result *= n--;
}
return result;
}
int main() {
int f4 = Factorial(4); // f4 == 24
}
Поскольку функция Factorial
отмечен consteval
, он гарантированно вызывается во время компиляции без принудительного использования в другом контексте, явно оцениваемом константой. Следовательно, использование непосредственных функций предлагает широкое применение в метапрограммировании и проверке во время компиляции (используется в библиотеке форматирования текста C ++ 20).
Вот пример использования непосредственных функций при выполнении функции во время компиляции:
void you_see_this_error_because_assertion_fails() {}
consteval void cassert(bool b) {
if (!b)
you_see_this_error_because_assertion_fails();
}
consteval void test() {
int x = 10;
cassert(x == 10); // ok
x++;
cassert(x == 11); // ok
x--;
cassert(x == 12); // fails here
}
int main() { test(); }
В этом примере компиляция не удалась, поскольку непосредственная функция вызвала функцию, которую нельзя использовать в константных выражениях. Другими словами, компиляция останавливается после неудачного утверждения.
Типичное сообщение об ошибке компиляции будет выглядеть следующим образом:
In function 'int main()':
in 'constexpr' expansion of 'test()'
in 'constexpr' expansion of 'cassert(x == 12)'
error: call to non-'constexpr' function 'you_see_this_error_because_assertion_fails()'
you_see_this_error_because_assertion_fails();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
[ ... ]
Вот еще один пример использования непосредственных функций в качестве конструкторов, который позволяет проверять аргументы во время компиляции:
#include <string_view>
#include <iostream>
void you_see_this_error_because_the_message_ends_with_exclamation_point() {}
struct checked_message {
std::string_view msg;
consteval checked_message(const char* arg)
: msg(arg) {
if (msg.ends_with('!'))
you_see_this_error_because_the_message_ends_with_exclamation_point();
}
};
void send_calm_message(checked_message arg) {
std::cout << arg.msg << '\n';
}
int main() {
send_calm_message("Hello, world");
send_calm_message("Hello, world!");
}
Компиляция здесь завершается с ошибкой:
In function 'int main()':
in 'constexpr' expansion of 'checked_message(((const char*)"Hello, world!"))'
error: call to non-'constexpr' function 'void you_see_this_error_because_the_message_ends_with_exclamation_point()'
you_see_this_error_because_the_message_ends_with_exclamation_point();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~
[ ... ]
Д
[ редактировать ]Вот пример вычисления функции времени компиляции на языке программирования D : [3]
int factorial(int n) {
if (n == 0)
return 1;
return n * factorial(n - 1);
}
// computed at compile time
enum y = factorial(0); // == 1
enum x = factorial(4); // == 24
В этом примере указана допустимая функция D, называемая «факториал», которая обычно оценивается во время выполнения. Использование enum
сообщает компилятору, что инициализатор переменных должен быть вычислен во время компиляции. Обратите внимание, что аргументы функции также должны быть разрешены во время компиляции. [4]
CTFE можно использовать для простого заполнения структур данных во время компиляции (версия D 2):
int[] genFactorials(int n) {
auto result = new int[n];
result[0] = 1;
foreach (i; 1 .. n)
result[i] = result[i - 1] * i;
return result;
}
enum factorials = genFactorials(13);
void main() {}
// 'factorials' contains at compile-time:
// [1, 1, 2, 6, 24, 120, 720, 5_040, 40_320, 362_880, 3_628_800,
// 39_916_800, 479_001_600]
CTFE можно использовать для генерации строк, которые затем анализируются и компилируются как код D в D.
Зиг
[ редактировать ]Вот пример вычисления функции времени компиляции на языке программирования Zig : [5]
pub fn factorial(n: usize) usize {
var result = 1;
for (1..(n + 1)) |i| {
result *= i;
}
return result;
}
pub fn main() void {
const x = comptime factorial(0); // == 0
const y = comptime factorial(4); // == 24
}
В этом примере указывается допустимая функция Zig под названием «факториал», которая обычно оценивается во время выполнения. Использование comptime
сообщает компилятору, что инициализатор переменных должен быть вычислен во время компиляции. Обратите внимание, что аргументы функции также должны быть разрешены во время компиляции.
Zig также поддерживает параметры времени компиляции. [6]
pub fn factorial(comptime n: usize) usize {
var result: usize = 1;
for (1..(n + 1)) |i| {
result *= i;
}
return result;
}
pub fn main() void {
const x = factorial(0); // == 0
const y = factorial(4); // == 24
}
CTFE можно использовать для создания общих структур данных во время компиляции:
fn List(comptime T: type) type {
return struct {
items: []T,
len: usize,
};
}
// The generic List data structure can be instantiated by passing in a type:
var buffer: [10]i32 = undefined;
var list = List(i32){
.items = &buffer,
.len = 0,
};
Ссылки
[ редактировать ]- ^ Дэвид Вандевурд, Edison Design Group (18 апреля 2003 г.). «Рефлексивное метапрограммирование в C ++» (PDF) . Проверено 19 июля 2015 г.
- ^ Габриэль Дос Рейс и Бьерн Страуструп (март 2010 г.). «Общие константные выражения для языков системного программирования. SAC-2010. 25-й симпозиум ACM по прикладным вычислениям» (PDF) .
- ^ Спецификация языка D 2.0: Функции
- ^ Спецификация языка D 2.0: Атрибуты
- ^ Zig 0.11.0 Справочник по языку: выражения времени компиляции
- ^ Zig 0.11.0 Справочник по языку: Параметры времени компиляции