Я пытаюсь создать 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, либо найти библиотеку, которая их уже предоставляет.