Я пытаюсь создать LUT на C++, который рассчитывается во время компиляции и просто помещается в память для использования другими модулями. Каждый термин в этом LUT можно вычислить во время компиляции, это просто мешанина констант и операций, таких как суммы, показатели степени и сферические функции Бесселя.
Пример расчета, из которого будет состоять один из элементов. Еще раз обратите внимание: каждая часть уравнения известна во время компиляции. Я просто хотел бы, чтобы компилятор сделал всю работу за меня и сгенерировал LUT.
Основываясь исключительно на встроенном C, я решил взаимодействовать с модулем C++, чтобы использовать возможность constexpr
, которая, как показали мои исследования, будет правильным механизмом для создания довольно большой 2D-таблицы поиска, не прибегая к сотням уродливых операторов инициализации массива. Моя мысль была такой: я мог бы написать несколько функций, пометить их constexpr
и все структуры, переменные и функции, с которыми они связаны, как constexpr
, и всё бы получилось.
Проблема, с которой я столкнулся, заключается в том, что расчет элементов в этом LUT рассчитывается с использованием математических функций c++ stl, таких как std::powf()
или std::cyl_bessel_jf()
, а эти функции — нет constexpr
. Они используют глобальную переменную статуса errno
непотокобезопасным образом и, таким образом, не являются повторными и, следовательно, не могут быть constexpr
. Прохладный.
Но для моего варианта использования это не должно иметь значения. Не имеет значения, имеет ли целевое приложение во время выполнения потокобезопасную реализацию std::powf()
Я хочу, чтобы компилятор просто вычислил их во время компиляции и поместил в память!
Это подводит меня к моему вопросу: constexpr
это не механизм C++, или я делаю что-то синтаксически глупое со своим кодом?
Потенциальная проблема здесь в том, что я вызываю foo()
из другого модуля C, а не из C++.
Код, иллюстрирующий пример, не вдаваясь в слишком много математической ерунды. Примечание. Я использую C++17 с gcc.
constexpr int t_cnt = 100;
constexpr int r_cnt = 100;
constexpr float _v_param = 1.0f;
constexpr float _R_param = 1.0f;
constexpr float _mu_param = 1.0f;
constexpr float exp_func (float t, float r)
{
return expf(-1.0f * powf(r, 2) * _v_param * t / powf(_R_param, 2));
}
struct LUT
{
constexpr LUT() : values()
{
for (auto t = 0; t < t_cnt; t++)
{
for (auto r = 0; r < r_cnt; r++)
{
values[t][r] = exp_func(t, r);
}
}
}
float values[t_cnt][r_cnt];
};
void foo ()
{
constexpr auto footable = LUT();
/*I would like to use footable in other parts of the code,
to get values like footable[0][1] without calculating it in runtime*/
}
Этот код генерирует следующую ошибку:
src/precompute.cpp: In function 'void foo()':
src/precompute.cpp:143:30: in 'constexpr' expansion of 'LUT()'
src/precompute.cpp:133:40: in 'constexpr' expansion of 'exp_func((float)LUT::__ct ::t, (float)LUT::__ct ::r)'
src/precompute.cpp:122:16: error: 'expf(-1.0e+2f)' is not a constant expression
122 | return expf(-1.0f * powf(r, 2) * _v_param * t / powf(_R_param, 2));
Я ожидаю, что смогу создать структуру LUT во время компиляции и просто читать из нее значения.
Да, в C23 есть версия constexpr
, но это не имеет значения для вашего кода, который является чистым C++. Не помечайте вопрос дважды тегами C и C++, если вам особенно не нравится идея быть отвергнутым.
У вас может быть отдельный фрагмент кода, который при запуске генерирует объявления C++ для нужных значений.
Незначительный момент: имена, начинающиеся с подчеркивания, всегда зарезервированы в глобальной области пространства имен. Поэтому _v_param
/_R_param
/_mu_param
запрещены там, где вы их используете. Идентификаторы, начинающиеся с подчеркивания, за которым следует прописная буква, например _R_param
, зарезервированы в каждом контексте. Вам не разрешено использовать их в своих целях.
@dbush Мне нравится писать такие служебные программы на Python, мне проще решать такие простые задачи, чем на C++.
Это подводит меня к вопросу: является ли constexpr не механизмом C++ для этого, или я делаю что-то синтаксически глупое со своим кодом?
Вы не делаете ничего плохого и также правы в том, что та часть функционала математических функций, которую вы в принципе хотите использовать, работает и в константных выражениях.
Проблема просто в том, что эти функции изначально не были написаны для использования во время компиляции, и нужно проделать работу, чтобы указать, как они должны вести себя во время компиляции (например, как обрабатывать errno
, как обрабатывать исключения с плавающей запятой) и тогда разработчики библиотеки должны реализовать эти спецификации.
Это справедливо для всех функций библиотеки, а не только для математических функций. constexpr
был представлен в C++11, и с тех пор все больше и больше базового языка и библиотеки стали доступны для использования во время компиляции. Что касается математических функций, эта работа также началась с C++23, и еще несколько функций будут constexpr
в C++26.
Вы всегда можете написать эти функции (с желаемым поведением) самостоятельно как constexpr
и использовать их. В этом нет ничего принципиально проблематичного, если вы определились с поведением исключений errno
и плавающей запятой.
Проблема реализации до C++20 заключалась в том, что в базовом языке не было синтаксиса, позволяющего использовать разные реализации одной и той же функции во время компиляции и во время выполнения. Но для математических функций вам это, вероятно, понадобится, например. для переключения записи errno
или переключения между реализацией C++ во время компиляции и более производительной реализацией ассемблера во время выполнения.
Потенциальная проблема здесь заключается в том, что я вызываю foo() из другого модуля C, а не из C++.
Это не проблема. Когда вы это сделаете, ничего не пойдет не так.
В целом создается впечатление, что компилятор С++ не может (или не предназначен для этого) делать то, что я хочу. Внутренне я знаю, что эти функции вычислимы во время компиляции, но их реализация более тонкая, учитывая errno
и исключения с плавающей запятой. Я бы попытался реализовать свои собственные функции, как рекомендует этот ответ, но мне также нужно решить цилиндрические функции Бесселя, и я не хочу делать это сам на C/C++. Как сказал @mark-ransom, я считаю, что лучший вариант действий — заставить скрипт Python генерировать LUT. Спасибо всем!
Вместо того, чтобы пытаться сделать все это одновременно с компиляцией основной программы, создайте отдельную программу, которая выводит необходимые объявления:
#include <cmath>
#include <cstdio>
constexpr int t_cnt = 5;
constexpr int r_cnt = 5;
constexpr float _v_param = 1.0f;
constexpr float _R_param = 1.0f;
constexpr float _mu_param = 1.0f;
float exp_func (float t, float r)
{
return expf(-1.0f * powf(r, 2) * _v_param * t / powf(_R_param, 2));
}
int main()
{
printf("static constexpr int t_cnt = %d;\n", t_cnt);
printf("static constexpr int r_cnt = %d;\n", r_cnt);
printf("static constexpr float values[t_cnt][r_cnt] = {\n");
for (auto t = 0; t < t_cnt; t++)
{
printf(" {");
for (auto r = 0; r < r_cnt; r++)
{
printf("%.7f, ", exp_func(t, r));
}
printf("},\n");
}
printf("};\n");
};
Запустите его:
[dbush@db-centos7 ~]$ g++ -std=c++11 -g -Wall -Wextra -o gen_values gen_values.cpp
[dbush@db-centos7 ~]$ ./gen_values > values.h
Результирующий файл:
static constexpr int t_cnt = 5;
static constexpr int r_cnt = 5;
static constexpr float values[t_cnt][r_cnt] = {
{1.0000000, 1.0000000, 1.0000000, 1.0000000, 1.0000000, },
{1.0000000, 0.3678795, 0.0183156, 0.0001234, 0.0000001, },
{1.0000000, 0.1353353, 0.0003355, 0.0000000, 0.0000000, },
{1.0000000, 0.0497871, 0.0000061, 0.0000000, 0.0000000, },
{1.0000000, 0.0183156, 0.0000001, 0.0000000, 0.0000000, },
};
Затем включите его в свой основной исходный файл:
#include "values.h"
void foo ()
{
// use values
}
если вы хотите, чтобы это было сделано во время компиляции, ваши функции должны быть
constexpr
. Большинство триггерных функций — нет. Вам нужно либо написать свою собственную версию constexpr, либо найти библиотеку, которая их уже предоставляет.