我找到了两种在编译时here和here初始化整数数组的好方法。
不幸的是,两者都不能直接转换为初始化浮点数组;我发现我在模板元编程方面不够合适,无法通过反复试验来解决这个问题。
首先让我声明一个用例:
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
或模板参数环境的限制。答案 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()
的合理近似值。
评论表明到目前为止的答案并不能解释发生了什么。所以,让我们把它分解成可消化的部分:
目标是生成一个填充了适当值序列的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)
的每次更改而改变。因此,基本上与上述声明相同,但不需要额外输入。
解决方案的第一步是将N
替换为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>();
下标运算符不是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) };
是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";
}
请注意,您需要使用-stdlib=libc++
,因为libstdc++
的{{1}}执行效率非常低。
另请注意,您需要一个index_sequence
函数对象(constexpr
和pi
都不是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)));
}