Я использую функторы для генерации кода, рассчитанного во время компиляции, следующим образом (я извиняюсь за длинный код, но я нашел единственный способ воспроизвести поведение):
#include <array>
#include <tuple>
template <int order>
constexpr auto compute (const double h)
{
std::tuple<std::array<double,order>,
std::array<double,order> > paw{};
auto xtab = std::get<0>(paw).data();
auto weight = std::get<1>(paw).data();
if constexpr ( order == 3 )
{
xtab[0] = - 1.0E+00;
xtab[1] = 0.0E+00;
xtab[2] = 1.0E+00;
weight[0] = 1.0 / 3.0E+00;
weight[1] = 4.0 / 3.0E+00;
weight[2] = 1.0 / 3.0E+00;
}
else if constexpr ( order == 4 )
{
xtab[0] = - 1.0E+00;
xtab[1] = - 0.447213595499957939281834733746E+00;
xtab[2] = 0.447213595499957939281834733746E+00;
xtab[3] = 1.0E+00;
weight[0] = 1.0E+00 / 6.0E+00;
weight[1] = 5.0E+00 / 6.0E+00;
weight[2] = 5.0E+00 / 6.0E+00;
weight[3] = 1.0E+00 / 6.0E+00;
}
for (auto & el : std::get<0>(paw))
el = (el + 1.)/2. * h ;
for (auto & el : std::get<1>(paw))
el = el/2. * h ;
return paw;
}
template <std::size_t n>
class Basis
{
public:
constexpr Basis(const double h_) :
h(h_),
paw(compute<n>(h)),
coeffs(std::array<double,n>())
{}
const double h ;
const std::tuple<std::array<double,n>,
std::array<double,n> > paw ;
const std::array<double,n> coeffs ;
constexpr double operator () (int i, double x) const
{
return 1. ;
}
};
template <std::size_t n,std::size_t p,typename Ltype,typename number=double>
class Functor
{
public:
constexpr Functor(const Ltype L_):
L(L_)
{}
const Ltype L ;
constexpr auto operator()(const auto v) const
{
const auto l = L;
// const auto l = L();
std::array<std::array<number,p+1>,p+1> CM{},CM0{},FM{};
const auto basis = Basis<p+1>(l);
typename std::remove_const<typename std::remove_reference<decltype(v)>::type>::type w{};
for (auto i = 0u; i < p + 1; ++i)
CM0[i][0] += l;
for (auto i = 0u ; i < p+1 ; ++i)
for (auto j = 0u ; j < p+1 ; ++j)
{
w[i] += CM0[i][j]*v[j];
}
for (auto b = 1u ; b < n-1 ; ++b)
for (auto i = 0u ; i < p+1 ; ++i)
for (auto j = 0u ; j < p+1 ; ++j)
{
w[b*(p+1)+i] += CM[i][j]*v[b*(p+1)+j];
w[b*(p+1)+i] += FM[i][j]*v[(b+1)*(p+1)+j];
}
return w ;
}
};
int main(int argc,char *argv[])
{
const auto nel = 4u;
const auto p = 2u;
std::array<double,nel*(p+1)> x{} ;
constexpr auto L = 1.;
// constexpr auto L = [](){return 1.;};
const auto A = Functor<nel,p,decltype(L)>(L);
const volatile auto y = A(x);
return 0;
}
Iскомпилировать с помощью GCC 8.2.0 с флагами:
-march=native -std=c++1z -fconcepts -Ofast -Wa,-adhln
И при просмотре сгенерированной сборки вычисление выполняется во время выполнения.
Если я изменяю две строки, которые комментируютсядля строк ниже, я обнаружил, что код действительно выполняется во время компиляции, и в сборку помещается только значение переменной volatile.
Я попытался сгенерировать меньший пример, который воспроизводит поведение, ноНебольшие изменения в коде действительно вычисляются во время компиляции.
Я почему-то понимаю, почему предоставление constexpr
лямбда-выражения помогает, но я хотел бы понять, почему предоставление double не будет работать в этом случае.В идеале я не хотел бы предоставлять лямбды, потому что это делает мой внешний интерфейс более беспорядочным.
Этот код является частью очень большой кодовой базы, поэтому, пожалуйста, не обращайте внимания на то, что код фактически вычисляет, я создал этот пример, чтобы показатьповедение и не более того.
Какой правильный способ предоставить двойник функтору и сохранить его как const
переменную-член без изменения поведения во время компиляции?
Почемунебольшие изменения в функции compute()
(например, другие небольшие изменения делают так же) действительно генерируют код времени компиляции?
Я хотел бы понять, каковы фактические условия для GCC, чтобы обеспечить эти компиляциивремя вычислений, так как фактическое приложение, в котором я работаю, требует этого.
Спасибо!