c++ - 如何在编译时初始化一个 float 组?

标签 c++ arrays templates c++14 constexpr

我发现了两种在编译时初始化整数数组的好方法 herehere .

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

首先让我声明一个用例:

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));
}

如您所见,就可用信息而言,此数组可以声明为constexpr

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

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

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

现在,第一个想到的想法是将 float 存储在一个 int 变量中——数组索引在计算后没有任何变化,因此假装它们是另一种类型(只要 byte-长度相等)完全没问题。

但是请看:

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

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

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

也不行;尽管人们可能认为 reinterpret_cast 确实做了这里需要的事情(即什么都没有),但它显然只适用于指针。

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

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

本质上是相同的问题:该枚举不能是 float 类型,并且该类型不能被屏蔽为 int


现在,尽管我只是在“假装 float 是一个 int”的路径上解决了这个问题,但我实际上并不喜欢这条路径(除了它不工作)。我更喜欢一种将 float 实际处理为 float 的方式(并且将 double 处理为 double),但我看不出有什么办法可以绕过强加的类型限制。

可悲的是,关于这个问题有很多问题,这些问题总是指整数类型,淹没了对这个专门问题的搜索。同样,关于将一种类型屏蔽为另一种类型的问题通常不会考虑 constexpr 或模板参数环境的限制。
参见 [1] [2] [3][4] [5]等等

最佳答案

假设您的实际目标是使用一种简洁的方法来初始化 float 数组,并且它不一定拼写为 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>();

这对我们有什么帮助?好吧,当我们可以调用一个以整数作为参数的函数时,我们可以使用 std::make_index_sequence<N>使用 std::size_t 的编译时序列来自 0N-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>{});
}

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

评论表明到目前为止的答案并不能完全解释发生了什么。因此,让我们将其分解为易于理解的部分:

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

    constexpr float array[N] = { f(0), f(1), ..., f(N-1) };
    

    在哪里f()是生成 constexpr 的合适函数.例如,f()可以定义为

    constexpr float f(int i) {
        return const_sin(double(i) * M_PI / 180.0 * 360.0 / double(Length);
    }
    

    但是,输入对 f(0) 的调用, f(1)等需要随着 N 的每次更改而更改.因此,应该进行与上述声明基本相同的操作,但无需额外输入。

  2. 解决方案的第一步是替换 float[N]通过 std:array<float, N> : 在 std::array<float, N> 时无法复制内置数组可以复制。也就是说,初始化可以委托(delegate)给参数化为 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>();
    
  3. 在函数中,我们不能简单地遍历数组,因为非 const下标运算符不是 constexpr .相反,数组需要在创建时进行初始化。如果我们一个参数包 std::size_t... I代表序列0, 1, .., N-1我们可以

    std::array<float, N>{ f(I)... };
    

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

    std::array<float, N>{ f(0), f(1), .., f(N-1) };
    

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

  4. 使用别名 std::make_index_sequence<N>是类型 std::index_sequence<0, 1, .., N-1> 的别名.实现的细节有点神秘,但是 std::make_index_sequence<N> , std::index_sequence<...> , friends 是 C++14 的一部分(它们是由 N3493 基于例如 this answer from me 提出的)。也就是说,我们需要做的就是调用一个参数类型为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>{});
    }
    

    此函数的[未命名]参数仅用于具有参数包std::size_t... I被推导。

关于c++ - 如何在编译时初始化一个 float 组?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34310866/

相关文章:

c++ - #pragma 有什么用?

C++1 1's "default”只能应用于特殊成员函数?

c++ - clang++ 中的 std::underlying_type 支持

javascript - 循环遍历对象数组并返回每个对象的键和值

c++ - 通过引用修改数组后,为什么它保持不变?

javascript - 在带有 async、await 的数组映射函数中使用类的 `this` 对象 - Nodejs、Javascript

c++ - 模板数据成员

c++ - 通过 QT Creator 以 snake case 形式生成重构方法

javascript - iCanHaz.js - 分离模板?

C++:尝试通过组合和帕斯卡三角来理解 constexpr