如何在编译时初始化浮点数组?

Zsa*_*sar 5 c++ arrays templates constexpr c++14

我已经发现了两个好方法在编译时间初始化积分阵列这里这里.

不幸的是,两者都不能直接转换为初始化浮点数组; 我发现我在模板元编程方面不够合适,无法通过反复试验来解决这个问题.

首先让我声明一个用例:

constexpr unsigned int SineLength  = 360u;
constexpr unsigned int ArrayLength = SineLength+(SineLength/4u);
constexpr double PI = 3.1415926535;

float array[ArrayLength];

void fillArray(unsigned int length)
{
  for(unsigned int i = 0u; i < length; ++i)
    array[i] = sin(double(i)*PI/180.*360./double(SineLength));
}
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,至于信息的可用性推移,这种阵列可以被宣布constexpr.

但是,对于链接的第一种方法,生成器函数f必须如下所示:

constexpr float f(unsigned int i)
{
  return sin(double(i)*PI/180.*360./double(SineLength));
}
Run Code Online (Sandbox Code Playgroud)

这意味着float需要类型的模板参数.这是不允许的.

现在,我想到的第一个想法是将float存储在一个int变量中 - 计算后数组索引没有任何反应,所以假装它们是另一种类型(只要字节长度相等) )完全没问题.

但请看:

constexpr int f(unsigned int i)
{
  float output = sin(double(i)*PI/180.*360./double(SineLength));
  return *(int*)&output;
}
Run Code Online (Sandbox Code Playgroud)

不是有效的constexpr,因为它包含的不仅仅是return语句.

constexpr int f(unsigned int i)
{
  return reinterpret_cast<int>(sin(double(i)*PI/180.*360./double(SineLength)));
}
Run Code Online (Sandbox Code Playgroud)

也不起作用; 尽管人们可能认为这reinterpret_cast完全符合这里所需要的东西(即什么都没有),但它显然只适用于指针.

在第二种方法之后,生成器函数看起来会略有不同:

template<size_t index> struct f
{
  enum : float{ value = sin(double(index)*PI/180.*360./double(SineLength)) };
};
Run Code Online (Sandbox Code Playgroud)

基本上是同一个问题:枚举不能是类型float,类型不能被掩盖int.


现在,即使我只是在"假装float是一个int" 的道路上接近问题,我实际上并不喜欢这条道路(除了它不起作用).我更喜欢一种实际处理floatas的方式float(并且也会处理doubleasdouble),但我认为没有办法绕过强加的类型限制.

遗憾的是,关于这个问题存在很多问题,这些问题总是涉及整体类型,淹没了对这个专门问题的搜索.类似地,关于将一种类型屏蔽为另一种类型的问题通常不考虑constexpr模板参数环境的限制.
[1] [2] [3][4] [5]等.

Die*_*ühl 13

假设您的实际目标是有一个简洁的方式来初始化浮点数的数组,它不一定拼写float array[N]或者double array[N]而是std::array<float, N> arraystd::array<double, N> array可以做到这一点.

数组类型的重要性是std::array<T, N>可以复制的 - 不像T[N].如果可以复制,则可以从函数调用中获取数组的内容,例如:

constexpr std::array<float, ArrayLength> array = fillArray<N>();
Run Code Online (Sandbox Code Playgroud)

这对我们有什么帮助?好吧,当我们可以调用一个以整数作为参数的函数时,我们可以使用来自to std::make_index_sequence<N>的编译时序列.如果我们有这个,我们可以使用基于索引的公式轻松初始化数组,如下所示:std::size_t0N-1

constexpr double const_sin(double x) { return x * 3.1; } // dummy...
template <std::size_t... I>
constexpr std::array<float, sizeof...(I)> fillArray(std::index_sequence<I...>) {
    return std::array<float, sizeof...(I)>{
            const_sin(double(I)*M_PI/180.*360./double(SineLength))...
        };
}

template <std::size_t N>
constexpr std::array<float, N> fillArray() {
    return fillArray(std::make_index_sequence<N>{});
}
Run Code Online (Sandbox Code Playgroud)

假设用于初始化数组元素的函数实际上是一个constexpr表达式,这种方法可以生成一个constexpr.那个const_sin()仅用于演示目的的功能就是这样,但显然,它并没有计算出合理的近似值sin(x).

评论表明,到目前为止的答案并没有完全解释发生了什么.所以,让我们把它分解成易消化的部分:

  1. 目标是生成一个constexpr充满合适的值序列的数组.但是,通过仅调整数组大小,可以轻松更改数组的大小N.也就是说,从概念上讲,目标是创造

    constexpr float array[N] = { f(0), f(1), ..., f(N-1) };
    
    Run Code Online (Sandbox Code Playgroud)

    哪个f()是合适的功能产生constexpr.例如,f()可以定义为

    constexpr float f(int i) {
        return const_sin(double(i) * M_PI / 180.0 * 360.0 / double(Length);
    }
    
    Run Code Online (Sandbox Code Playgroud)

    然而,在调用打字f(0),f(1)等需要用的每一个变化而变化N.因此,基本上与上述声明相同,但不需要额外输入.

  2. 解决方案的第一步是替换float[N]std:array<float, N>:内置数组在复制时std::array<float, N>无法复制.也就是说,初始化可以委托给参数化的函数N.也就是说,我们使用

    template <std::size_t N>
    constexpr std::array<float, N> fillArray() {
        // some magic explained below goes here
    }
    constexpr std::array<float, N> array = fillArray<N>();
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在函数内我们不能简单地遍历数组,因为非const下标运算符不是a constexpr.相反,数组需要在创建时初始化.如果我们一个参数包std::size_t... I,它代表0, 1, .., N-1我们可以做的序列

    std::array<float, N>{ f(I)... };
    
    Run Code Online (Sandbox Code Playgroud)

    因为扩展实际上等同于打字

    std::array<float, N>{ f(0), f(1), .., f(N-1) };
    
    Run Code Online (Sandbox Code Playgroud)

    所以问题就变成了:如何获得这样的参数包?我不认为它可以直接在函数中获得,但可以通过调用具有合适参数的另一个函数来获得它.

  4. using alias std::make_index_sequence<N>是该类型的别名std::index_sequence<0, 1, .., N-1>.实现的细节有点晦涩,但是std::make_index_sequence<N>,std::index_sequence<...>和朋友是C++ 14的一部分(它们是由N3493提出的,例如基于我的回答).也就是说,我们需要做的就是使用类型参数调用辅助函数std::index_sequence<...>并从那里获取参数包:

    template <std::size_t...I>
    constexpr std::array<float, sizeof...(I)>
    fillArray(std::index_sequence<I...>) {
        return std::array<float, sizeof...(I)>{ f(I)... };
    }
    template <std::size_t N>
    constexpr std::array<float, N> fillArray() {
        return fillArray(std::make_index_sequence<N>{});
    }
    
    Run Code Online (Sandbox Code Playgroud)

    此函数的[unnamed]参数仅用于std::size_t... I推导参数包.


归档时间:

查看次数:

1695 次

最近记录:

9 年,8 月 前