openlang

CTFE срабатывает там, где этого требует объявление

У меня долго была неправильная картина CTFE в D. Казалось, что если функция годится для вычисления при компиляции, компилятор сам вычислит её везде, где аргументы известны. На деле он делает это только там, где значение при компиляции обязательно нужно.

import std.stdio;

int sumSquares(int n)
{
    if (!__ctfe)
        writeln("sumSquares ran at run time");
    int total = 0;
    foreach (i; 0 .. n)
        total += i * i;
    return total;
}

enum a = sumSquares(4);       // interpreted by the compiler
immutable b = sumSquares(4);  // module scope, also compile time

void main()
{
    auto c = sumSquares(4);   // ordinary call, prints the line
    writeln(a, " ", b, " ", c);
}

Программа один раз печатает строку про время выполнения, из c, а потом 14 14 14. И enum, и immutable на уровне модуля требуют значения во время компиляции, поэтому там sumSquares интерпретируется и __ctfe истинно. auto в main - обычный вызов, хотя ту же функцию с тем же аргументом только что дважды проинтерпретировали.

Теперь при какой-нибудь уборке кода замените этот auto на enum. В месте вызова меняется одно ключевое слово, а у функции тихо появилось новое требование - интерпретатор должен уметь выполнить её целиком. Неужели ключевое слово в объявлении - единственная подсказка для читателя?

Переведено с английскогоПоказать оригинал

Разговор продолжается 4

Следуя доводам
  1. Ссылка на ответ

    То есть просит слово при переменной, а не функция. Тогда и immutable d = sumSquares(4); внутри main, видимо, проходит через CTFE. Значению, которое никогда не изменится, ждать нечего, так что компилятор вполне может подставить его сам.

    Переведено с английскогоПоказать оригинал
  2. Ссылка на ответ

    Нет. immutable означает, что значение не меняется. О моменте вычисления в нём ничего не сказано. Локальный immutable инициализируется при выполнении main, и строка про время выполнения печатается дважды. Внутри функции пишите enum или static immutable. Статической локальной переменной нужен инициализатор времени компиляции, поэтому она проходит через CTFE.

    Переведено с английскогоПоказать оригинал
  3. Ссылка на ответ

    Есть забавный способ увидеть, как появляется это требование. Пусть sumSquares прибавляет rand() % 2 из core.stdc.stdlib, и скомпилируйте снова. Теперь падают и enum, и immutable на уровне модуля, потому что интерпретатор не может выполнить C-функцию без исходного кода. Строка с auto в main спокойно компилируется и просто вызывает её.

    Так что на ваш вопрос ответ - да, ключевое слово и есть подсказка. Работает это и в обратную сторону. enum служит дешёвой проверкой, что функция всё ещё интерпретируется, и компилятор пожалуется сразу, как только её кто-то сломает.

    Переведено с английскогоПоказать оригинал
  4. Ссылка на ответ

    Спасибо, теперь понятно. На самом деле речь шла о таблице поиска, и она стала static immutable, а не enum. enum с типом массива подставляется литералом везде, где его упоминают, так что каждое использование создаёт новый массив во время выполнения, а таблица static immutable существует в бинарнике в одном экземпляре.

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. Почему эта сумма в стиле CPS переполняет стек только в Debug?

    Проверьте, пожалуйста, правильно ли я это понимаю. Сумма в стиле передачи продолжений, написанная как упражнение. dotnet run…

    Ответы 5fsharp
  2. Один Int, две платформы, два ответа

    Это всплыло при запуске одного и того же модуля Gleam на обеих целевых платформах, и это скорее факт о среде выполнения, чем…

    Ответы 3gleam
  3. Фигурные скобки вокруг expr решают, что видит компилятор байткода

    expr без фигурных скобок - единственная привычка в Tcl, которую хочется запретить! Выглядит как придирка к стилю, а на деле н…

    Ответы 16tcl