Создание вычисляемой во время компиляции таблицы поиска, которая использует математические функции для вычисления элементов

Я пытаюсь создать 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 во время компиляции и просто читать из нее значения.

если вы хотите, чтобы это было сделано во время компиляции, ваши функции должны быть constexpr. Большинство триггерных функций — нет. Вам нужно либо написать свою собственную версию constexpr, либо найти библиотеку, которая их уже предоставляет.

NathanOliver 21.08.2024 00:59

Да, в C23 есть версия constexpr, но это не имеет значения для вашего кода, который является чистым C++. Не помечайте вопрос дважды тегами C и C++, если вам особенно не нравится идея быть отвергнутым.

Jonathan Leffler 21.08.2024 01:09

У вас может быть отдельный фрагмент кода, который при запуске генерирует объявления C++ для нужных значений.

dbush 21.08.2024 01:37

Незначительный момент: имена, начинающиеся с подчеркивания, всегда зарезервированы в глобальной области пространства имен. Поэтому _v_param/_R_param/_mu_param запрещены там, где вы их используете. Идентификаторы, начинающиеся с подчеркивания, за которым следует прописная буква, например _R_param, зарезервированы в каждом контексте. Вам не разрешено использовать их в своих целях.

user17732522 21.08.2024 02:46

@dbush Мне нравится писать такие служебные программы на Python, мне проще решать такие простые задачи, чем на C++.

Mark Ransom 22.08.2024 00:33
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
84
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Это подводит меня к вопросу: является ли 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. Спасибо всем!

user26912092 22.08.2024 18:05

Вместо того, чтобы пытаться сделать все это одновременно с компиляцией основной программы, создайте отдельную программу, которая выводит необходимые объявления:

#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
}

Другие вопросы по теме