【问题标题】:Define and declare a const std::array with constexpr if用 constexpr if 定义和声明一个 const std::array
【发布时间】:2021-03-28 05:12:28
【问题描述】:

我正在尝试实现 Gauss-Legendre 求积,我想要一个模板函数 它将点数作为模板参数。 现在我有这个:

template<int number_of_quadrature_points>
double gaussian_quadrature_integral_core(double (*f)(double), double from, double to){
    double scaling_factor = (to-from)/2;   
    double average_factor = (from+to)/2;
    std::array<double, number_of_quadrature_points> factors;
    std::array<double, number_of_quadrature_points> points;
    if constexpr(number_of_quadrature_points == 2){
        factors = {1, 1};
        points = {-1.0/sqrt(3), 1.0/sqrt(3)};
    }
    if constexpr(number_of_quadrature_points == 3){
        factors = {5.0/9.0, 8.0/9.0, 5.0/9.0};
        points = {-sqrt(3.0/5.0), 0, sqrt(3.0/5.0)};
    }
    
    double sum = 0;

    for(int i = 0; i < number_of_quadrature_points; i++){
        sum += factors.at(i)*((*f)(scaling_factor*points.at(i)+average_factor));
    }

    sum *= scaling_factor;
    return sum;
}

如你所见,当模板参数改变时,不仅数组大小改变,内容也改变,但对于给定大小,内容是众所周知的。出于这个原因,我认为如果 std::arrays 是 const static 会更好,因为该函数会被多次调用。

现在我只设法使用 if constexpr 来声明数组,但是我怎样才能使用它来定义和声明数组,以便它在 if constexpr 范围之外可见并且数组只定义一次?

【问题讨论】:

  • 如果有人用不同于 2 或 3 的 N 调用你的函数会发生什么? if constexpr 很棒,但在这种情况下我会选择一个专门的模板(用于因素/点选择)。
  • @Cedric 这确实是个问题,现在我只想做一个静态断言,但我会重新考虑它,因为您的评论让我意识到这样的解决方案可能并不优雅。

标签: c++ arrays stl containers constexpr


【解决方案1】:

添加两个辅助函数就足够了(如果您使用的是 C++20):

template<unsigned N>
constexpr auto init_factors() {
    std::array<double, N> rv;
    if constexpr(N == 2){
        rv = {1., 1.};
    } else {
        rv = {5.0/9.0, 8.0/9.0, 5.0/9.0};
    }
    return rv;
}

template<unsigned N>
constexpr auto init_points() {
    std::array<double, N> rv;
    if constexpr(N == 2){
        rv = {-1.0/std::sqrt(3.), 1.0/std::sqrt(3.)};
    } else {
        rv = {-std::sqrt(3.0/5.0), 0, std::sqrt(3.0/5.0)};
    }
    return rv;
}

template<unsigned number_of_quadrature_points>
double gaussian_quadrature_integral_core(double (*f)(double), double from,
                                                              double to)
{
    static constexpr auto factors = init_factors<number_of_quadrature_points>();
    static constexpr auto points = init_points<number_of_quadrature_points>();
[...]

为防止使用错误的点数,您可以添加static_assert

template<unsigned number_of_quadrature_points>
double
gaussian_quadrature_integral_core(double (*f)(double), double from,
                                                       double to)
{
    static_assert(number_of_quadrature_points==2||number_of_quadrature_points==3);

...如果您想稍后进行专业化,则使用 SFINAE 阻止匹配:

#include <type_traits>

template<unsigned number_of_quadrature_points>
std::enable_if_t<number_of_quadrature_points==2||number_of_quadrature_points==3,
                 double>
gaussian_quadrature_integral_core(double (*f)(double), double from,
                                                       double to)
{

【讨论】:

  • 我也想到了类似的事情,但我在想如果没有辅助函数是否可能。不过,这仍然是一个很好的解决方案,谢谢。
  • @KarolSzustakowski 如果您愿意,请使用 lambda。
【解决方案2】:

你可能有模板变量:

template <std::size_t N>
static constexpr std::array<double, N> factors;

template <std::size_t N>
static constexpr std::array<double, N> points;

template <>
constexpr std::array<double, 2> factors<2>{{1, 1}};
template <>
constexpr std::array<double, 2> points<2>{{-1.0 / sqrt(3), 1.0 / sqrt(3)}};

template <>
constexpr std::array<double, 3> factors<3>{{5.0 / 9.0, 8.0 / 9.0, 5.0 / 9.0}};
template <>
constexpr std::array<double, 3> points<3>{{-sqrt(3.0 / 5.0), 0, sqrt(3.0 / 5.0)}};

然后

template<int number_of_quadrature_points>
double gaussian_quadrature_integral_core(double (*f)(double), double from, double to)
{
    const double scaling_factor = (to - from) / 2;   
    const double average_factor = (from + to) / 2;
    double sum = 0;

    for(int i = 0; i < number_of_quadrature_points; i++){
        sum += factors<number_of_quadrature_points>[i]
           * ((*f)(scaling_factor * points<number_of_quadrature_points>[i] + average_factor));
    }

    sum *= scaling_factor;
    return sum;
}

Demo

请注意,如果没有 constexpr sqrtstd:: 没有),则必须将 constexpr 替换为 const

【讨论】:

  • ... 它甚至在 C++14 中也能工作,这很好。
【解决方案3】:

您可以在本主题中使用类似的内容: is there a way to put condition on constant value parameter in C++ template specialization?

因此,我们使用std::enable_if 和 SFINAE 创建了两个模板特化。我们通过模板参数number_of_quadrature_points来区分它们。这样我们就有了不需要多次定义和实例化的全局参数。此代码使用 c++17 编译。

另外我建议使用带有std::function<> 的现代方法,而不是指向函数的指针。

#include <array>
#include <cmath>
#include <iostream>
#include <functional>

template<int number_of_quadrature_points, typename E=void>
struct gaussian_quadrature_params
{

};

template<int number_of_quadrature_points>
struct gaussian_quadrature_params<number_of_quadrature_points, std::enable_if_t<(number_of_quadrature_points==2)> >
{
    constexpr static const std::array<double, number_of_quadrature_points> factors = {1, 1};
    constexpr static const std::array<double, number_of_quadrature_points> points = {-1.0/sqrt(3), 1.0/sqrt(3)};
};

template<int number_of_quadrature_points>
struct gaussian_quadrature_params<number_of_quadrature_points, std::enable_if_t<(number_of_quadrature_points==3)> >
{
    constexpr static const std::array<double, number_of_quadrature_points> factors = {5.0/9.0, 8.0/9.0, 5.0/9.0};
    constexpr static const std::array<double, number_of_quadrature_points> points = {-sqrt(3.0/5.0), 0, sqrt(3.0/5.0)};
};


double f(double x)
{
    return x;
}


template<int number_of_quadrature_points>
double gaussian_quadrature_integral_core(std::function<double(double)> f, double from, double to){
    double scaling_factor = (to-from)/2;   
    double average_factor = (from+to)/2;
    
    double sum = 0;

    for(int i = 0; i < number_of_quadrature_points; i++){
        sum += gaussian_quadrature_params<number_of_quadrature_points>::factors.at(i)*(f(scaling_factor*gaussian_quadrature_params<number_of_quadrature_points>::points.at(i)+average_factor));
    }

    sum *= scaling_factor;
    return sum;
}

int main()
{
   std::cout << gaussian_quadrature_integral_core<2>(f, -1.0, 1.0) << std::endl;
   std::cout << gaussian_quadrature_integral_core<3>(f, -1.0, 1.0) << std::endl;
}

【讨论】:

  • 您可能只是对 template&lt;&gt; struct gaussian_quadrature_params&lt;2&gt; 进行了专门化,并带有启动器(您可以保留启动器以允许实际公式(如大于 42 的奇数))。
猜你喜欢
  • 1970-01-01
  • 2023-04-08
  • 2017-12-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多