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

时间:2015-12-16 11:29:16

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

我找到了两种在编译时herehere初始化整数数组的好方法。

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

首先让我声明一个用例:

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

现在,即使我只是在“假装floatint”的路径上找到问题,我实际上并不喜欢这条路(除了它不起作用)。我更喜欢一种实际处理floatfloat的方法(并且double只能处理double),但我认为无法绕过强加的类型限制。

遗憾的是,有很多关于这个问题的问题,总是提到整体类型,淹没了对这个专业问题的搜索。同样,将一种类型屏蔽为另一种类型的问题通常不考虑constexpr或模板参数环境的限制。
请参阅[1] [2] [3][4] [5]等。

3 个答案:

答案 0 :(得分:12)

假设您的实际目标是以简洁的方式初始化浮点数数组,并且它不必拼写为float array[N]double array[N],而是std::array<float, N> array或{ {1}}这可以做到。

数组类型的重要性在于std::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的编译时序列0。如果我们有这个,我们可以使用基于索引的公式轻松初始化数组,如下所示:

N-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()的合理近似值。

评论表明到目前为止的答案并不能解释发生了什么。所以,让我们把它分解成可消化的部分:

  1. 目标是生成一个填充了适当值序列的sin(x)数组。但是,通过仅调整数组大小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)的每次更改而改变。因此,基本上与上述声明相同,但不需要额外输入。

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

    N
  3. 在函数中我们不能简单地遍历数组,因为非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>(); 下标运算符不是const。相反,数组需要在创建时初始化。如果我们 参数包constexpr代表序列std::size_t... I,我们可以只做

    0, 1, .., N-1

    因为扩展实际上等同于输入

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

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

  4. 使用别名std::array<float, N>{ f(0), f(1), .., f(N-1) }; std::make_index_sequence<N>类型的别名。实现的细节有点神秘但是std::index_sequence<0, 1, .., N-1>std::make_index_sequence<N>和朋友是C ++ 14的一部分(它们是由N3493基于例如{{3}提出的}})。也就是说,我们需要做的就是调用一个类型为std::index_sequence<...>的参数的辅助函数,并从那里获取参数包:

    std::index_sequence<...>

    此函数的[unnamed]参数仅用于推导参数包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>{}); }

答案 1 :(得分:2)

这是一个生成sin值表的工作示例,您可以通过传递不同的函数对象轻松地适应对数表

#include <array>    // array
#include <cmath>    // sin
#include <cstddef>  // size_t
#include <utility>  // index_sequence, make_index_sequence
#include <iostream>

namespace detail {

template<class Function, std::size_t... Indices>
constexpr auto make_array_helper(Function f, std::index_sequence<Indices...>)
        -> std::array<decltype(f(std::size_t{})), sizeof...(Indices)>
{
        return {{ f(Indices)... }};
}

}       // namespace detail

template<std::size_t N, class Function>
constexpr auto make_array(Function f)
{
        return detail::make_array_helper(f, std::make_index_sequence<N>{});
}

static auto const pi = std::acos(-1);
static auto const make_sin = [](int x) { return std::sin(pi * x / 180.0); };
static auto const sin_table = make_array<360>(make_sin);

int main() 
{
    for (auto elem : sin_table)
        std::cout << elem << "\n";
}

Live Example.

请注意,您需要使用-stdlib=libc++,因为libstdc++的{​​{1}}执行效率非常低。

另请注意,您需要一个index_sequence函数对象(constexprpi都不是std::sin)才能在编译时真正初始化数组而不是程序初始化。

答案 2 :(得分:-2)

我只是为文档保留这个答案。正如评论所说,我被gcc宽容地误导了。当使用f(42)时失败,例如作为模板参数,如下所示:

std::array<int, f(42)> asdf;

抱歉,这不是解决方案

在两个不同的constexpr函数中将float的计算和转换分离为int:

constexpr int floatAsInt(float float_val) {
  return *(int*)&float_val;
}

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