C++ 如何在编译时初始化浮点数组?
我发现了两种在编译时和编译时初始化整数数组的好方法 不幸的是,两者都不能直接转换为初始化浮点数组;我发现我不够适合模板元编程,无法通过反复试验来解决这个问题 首先让我声明一个用例:C++ 如何在编译时初始化浮点数组?,c++,arrays,templates,c++14,constexpr,C++,Arrays,Templates,C++14,Constexpr,我发现了两种在编译时和编译时初始化整数数组的好方法 不幸的是,两者都不能直接转换为初始化浮点数组;我发现我不够适合模板元编程,无法通过反复试验来解决这个问题 首先让我声明一个用例: constexpr unsigned int SineLength = 360u; constexpr unsigned int ArrayLength = SineLength+(SineLength/4u); constexpr double PI = 3.1415926535; float array[Ar
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));
}
这意味着需要float
类型的模板参数。这是不允许的
现在,我想到的第一个想法是将浮点存储在一个int变量中——数组索引在计算后不会发生任何变化,因此假装它们是另一种类型(只要字节长度相等)是完全正确的
但请看:
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)));
}
与本质上相同的问题是:枚举不能是float
类型,并且不能将该类型屏蔽为int
现在,尽管我只是在“假装
浮点
是整数
”的路径上解决了这个问题,但我实际上并不喜欢这条路径(除了它不起作用之外)。我更喜欢一种实际处理float
的方法,就像处理float
一样(也可以像处理double
一样处理double
),但我认为没有办法绕过强加的类型限制
遗憾的是,关于这个问题有很多问题,它们总是涉及积分类型,淹没了对这个专门问题的搜索。类似地,关于掩蔽一个类型的问题通常不考虑一个<代码> CONTXPRP或模板参数环境的限制。
请参阅和等。假设您的实际目标是用一种简洁的方法初始化浮点数数组,它不一定拼写为
float array[N]
或double array[N]
,而是std::array
或std::array
,这是可以做到的
数组类型的重要性在于可以复制std::array
,这与T[N]
不同。如果可以复制,则可以通过函数调用获取数组的内容,例如:
constexpr std::array<float, ArrayLength> array = fillArray<N>();
假设用于初始化数组元素的函数实际上是一个constepr
表达式,这种方法可以生成constepr
。函数const_sin()
仅用于演示,但显然,它没有计算出sin(x)的合理近似值
这些评论表明,迄今为止的答案并不能完全解释发生了什么。那么,让我们把它分解成可消化的部分:
constexpr
数组,该数组中填充了合适的值序列。但是,只需调整数组大小N
,就可以轻松更改数组大小。也就是说,从概念上讲,目标是创造
constexpr float array[N] = { f(0), f(1), ..., f(N-1) };
其中f()
是生成constepr
的合适函数。例如,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
:无法复制内置数组,而可以复制std::array
。也就是说,可以将初始化委托给由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>();
因为扩展实际上等同于键入
std::array<float, N>{ f(0), f(1), .., f(N-1) };
std::数组{f(0),f(1),…,f(N-1)};
所以问题变成了:如何获得这样一个参数包?我不认为它可以直接在函数中获得,但可以通过调用另一个具有合适参数的函数来获得std::make_index_序列
是类型std::index_序列
的别名。实现的细节有点神秘,但是std::make_index_sequence
,std::index_sequence
,friends是C++14的一部分(它们是基于,例如on提出的)。也就是说,我们所需要做的就是调用一个带有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>{});
}
模板
constexpr std::数组
fillArray(标准::索引\u序列){
返回std::数组{f(I)…..};
}
模板
constexpr std::数组fillArray(){
返回fillArray(std::make_index_sequence{});
}
此函数的[unnamed]参数仅用于使参数packstd::size\u t。。。我可以推断
我只是把这个答案留作文档。正如评论所说,我被gcc的宽容误导了。当使用
f(42)
作为模板参数时,它会失败,例如:
constexpr float f(unsigned int i)
{
return sin(double(i)*PI/180.*360./double(SineLength));
}
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>{});
}
std::array<int, f(42)> asdf;
下面是一个生成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++
对索引序列的实现效率非常低
还要注意,您需要一个constepr
函数对象(pi
和std::sin
都不是constepr
),以便在编译时而不是在程序初始化时真正初始化数组 如果要在编译时初始化浮点数组,有几个问题需要克服:
std::array
有点破绽,因为在可变constexpr std::array的情况下,操作符[]
不是constexpr(我相信这将在下一版本的标准中得到修复)
st
std::array<int, f(42)> asdf;
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)));
}
#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";
}
#include <iostream>
#include <cmath>
#include <utility>
#include <cassert>
#include <string>
#include <vector>
namespace cpputil {
// a fully constexpr version of array that allows incomplete
// construction
template<size_t N, class T>
struct array
{
// public constructor defers to internal one for
// conditional handling of missing arguments
constexpr array(std::initializer_list<T> list)
: array(list, std::make_index_sequence<N>())
{
}
constexpr T& operator[](size_t i) noexcept {
assert(i < N);
return _data[i];
}
constexpr const T& operator[](size_t i) const noexcept {
assert(i < N);
return _data[i];
}
constexpr T& at(size_t i) noexcept {
assert(i < N);
return _data[i];
}
constexpr const T& at(size_t i) const noexcept {
assert(i < N);
return _data[i];
}
constexpr T* begin() {
return std::addressof(_data[0]);
}
constexpr const T* begin() const {
return std::addressof(_data[0]);
}
constexpr T* end() {
// todo: maybe use std::addressof and disable compiler warnings
// about array bounds that result
return &_data[N];
}
constexpr const T* end() const {
return &_data[N];
}
constexpr size_t size() const {
return N;
}
private:
T _data[N];
private:
// construct each element from the initialiser list if present
// if not, default-construct
template<size_t...Is>
constexpr array(std::initializer_list<T> list, std::integer_sequence<size_t, Is...>)
: _data {
(
Is >= list.size()
?
T()
:
std::move(*(std::next(list.begin(), Is)))
)...
}
{
}
};
// convenience printer
template<size_t N, class T>
inline std::ostream& operator<<(std::ostream& os, const array<N, T>& a)
{
os << "[";
auto sep = " ";
for (const auto& i : a) {
os << sep << i;
sep = ", ";
}
return os << " ]";
}
}
namespace trig
{
constexpr double pi() {
return M_PI;
}
template<class T>
auto constexpr to_radians(T degs) {
return degs / 180.0 * pi();
}
// compile-time computation of a factorial
constexpr double factorial(size_t x) {
double result = 1.0;
for (int i = 2 ; i <= x ; ++i)
result *= double(i);
return result;
}
// compile-time replacement for std::pow
constexpr double power(double x, size_t n)
{
double result = 1;
while (n--) {
result *= x;
}
return result;
}
// compute a term in a taylor expansion at compile time
constexpr double taylor_term(double x, size_t i)
{
int powers = 1 + (2 * i);
double top = power(x, powers);
double bottom = factorial(powers);
auto term = top / bottom;
if (i % 2 == 1)
term = -term;
return term;
}
// compute the sin(x) using `terms` terms in the taylor expansion
constexpr double taylor_expansion(double x, size_t terms)
{
auto result = x;
for (int term = 1 ; term < terms ; ++term)
{
result += taylor_term(x, term);
}
return result;
}
// compute our interpolatable table as a constexpr
template<size_t N = 1024>
struct sin_table : cpputil::array<N, double>
{
static constexpr size_t cache_size = N;
static constexpr double step_size = (pi() / 2) / cache_size;
static constexpr double _360 = pi() * 2;
static constexpr double _270 = pi() * 1.5;
static constexpr double _180 = pi();
static constexpr double _90 = pi() / 2;
constexpr sin_table()
: cpputil::array<N, double>({})
{
for(int slot = 0 ; slot < cache_size ; ++slot)
{
double val = trig::taylor_expansion(step_size * slot, 20);
(*this)[slot] = val;
}
}
double checked_interp_fw(double rads) const {
size_t slot0 = size_t(rads / step_size);
auto v0 = (slot0 >= this->size()) ? 1.0 : (*this)[slot0];
size_t slot1 = slot0 + 1;
auto v1 = (slot1 >= this->size()) ? 1.0 : (*this)[slot1];
auto ratio = (rads - (slot0 * step_size)) / step_size;
return (v1 * ratio) + (v0 * (1.0-ratio));
}
double interpolate(double rads) const
{
rads = std::fmod(rads, _360);
if (rads < 0)
rads = std::fmod(_360 - rads, _360);
if (rads < _90) {
return checked_interp_fw(rads);
}
else if (rads < _180) {
return checked_interp_fw(_90 - (rads - _90));
}
else if (rads < _270) {
return -checked_interp_fw(rads - _180);
}
else {
return -checked_interp_fw(_90 - (rads - _270));
}
}
};
double sine(double x)
{
if (x < 0) {
return -sine(-x);
}
else {
constexpr sin_table<> table;
return table.interpolate(x);
}
}
}
void check(float degs) {
using namespace std;
cout << "checking : " << degs << endl;
auto mysin = trig::sine(trig::to_radians(degs));
auto stdsin = std::sin(trig::to_radians(degs));
auto error = stdsin - mysin;
cout << "mine=" << mysin << ", std=" << stdsin << ", error=" << error << endl;
cout << endl;
}
auto main() -> int
{
check(0.5);
check(30);
check(45.4);
check(90);
check(151);
check(180);
check(195);
check(89.5);
check(91);
check(270);
check(305);
check(360);
return 0;
}
checking : 0.5
mine=0.00872653, std=0.00872654, error=2.15177e-09
checking : 30
mine=0.5, std=0.5, error=1.30766e-07
checking : 45.4
mine=0.712026, std=0.712026, error=2.07233e-07
checking : 90
mine=1, std=1, error=0
checking : 151
mine=0.48481, std=0.48481, error=2.42041e-08
checking : 180
mine=-0, std=1.22465e-16, error=1.22465e-16
checking : 195
mine=-0.258819, std=-0.258819, error=-6.76265e-08
checking : 89.5
mine=0.999962, std=0.999962, error=2.5215e-07
checking : 91
mine=0.999847, std=0.999848, error=2.76519e-07
checking : 270
mine=-1, std=-1, error=0
checking : 305
mine=-0.819152, std=-0.819152, error=-1.66545e-07
checking : 360
mine=0, std=-2.44929e-16, error=-2.44929e-16