是否可以在编译时评估数组?

Posted

技术标签:

【中文标题】是否可以在编译时评估数组?【英文标题】:Is it possible to evaluate array on compilation time? 【发布时间】:2016-02-14 07:42:25 【问题描述】:

我需要存储前 N 个斐波那契数的数组。

const int N = 100;
long long int fib[N] = 0;
fib[0] = 1;
fib[1] = 1;
for(int i = 2; i < N; ++i)
    fib[i] = fib[i-2] + fib[i-1];
return 0;

是否可以制作 fib[] constexpr,并在编译时以某种方式对其进行评估?

【问题讨论】:

使用向量 fib; 目前的声明方式有什么问题?所有IS都在编译时评估。顺便说一句,如果您使用 C++,那么您不妨使用vector 我希望它在编译时预先计算,向量不会帮助我达到这个目标。我希望数组是 constexpr。 使用模板结构或递归,你对这两个感兴趣吗? 这是possible 和constexpr,但我不知道有什么特别的 方式。 【参考方案1】:

首先你必须在编译时编写斐波那契算法,所以考虑以下:

template <size_t N>
struct Fibo 
    static constexpr const size_t value Fibo<N-2>::value + Fibo<N-1>::value;
;

template <>
struct Fibo<0> 
    static constexpr const size_t value 1;
;

template <>
struct Fibo<1> 
    static constexpr const size_t value 1;
;

你可以这么简单地使用它:

std::cout << Fibo<0>::value << std::endl;
std::cout << Fibo<1>::value << std::endl;
std::cout << Fibo<2>::value << std::endl;
std::cout << Fibo<3>::value << std::endl;
std::cout << Fibo<10>::value << std::endl;
std::cout << Fibo<50>::value << std::endl;

输出值为:

1
1
2
3
89
20365011074

但这仍然不是你要找的。​​p>

我不知道你是否可以制作 constexpr 数组(但可能有可能),但你可以做的略有不同。考虑:

template <size_t N>
struct Storage 
    static size_t data[N+1];
;

template <size_t N> size_t Storage<N>::data[N+1] ;

template <size_t N, size_t F>
struct Filler 
    static constexpr void fill () 
        Storage<N>::data[F] = Fibo<F>::value;
        Filler<N, F-1>::fill ();
    
;

template <size_t N>
struct Filler<N, 0> 
    static constexpr void fill ()  
        Storage<N>::data[0] = Fibo<0>::value;
    
;

template <size_t N>
struct Calc 
    static constexpr void calc ()         
        Filler<N, N>::fill ();
    
;

用法如下:

constexpr const size_t N = 12;

Calc<N>::calc ();
size_t* ptr = Storage<N>::data;

for (int i = 0; i <= N; ++i) 
    std::cout << ptr[i] << std::endl;

和输出:

1
1
2
3
5
8
13
21
34
55
89
144
233

这里重要的是 Storage 类,它存储我们的数组和适当数量的元素。

一般Filler类(带有两个模板参数)用于任何可以传递的F值,除了值0。因为如果我们到达0索引,我们不想再次调用@ 987654330@ 成员函数,因为我们已经完成了。这就是Filler 类存在部分特化的原因。

希望我能帮上忙。

【讨论】:

请注意,fib(10) 是 55 而不是 89(即 fib(11))。问题是 fib(0) == 0 而不是 1。但这在问题中已经不正确了。【参考方案2】:

有办法(丑陋的),但我想不出别的办法了。

#include <iostream>
#include <cmath>

constexpr unsigned long long f(int x)

    return 1/sqrt(5)*pow(((1+sqrt(5))/2),x) - 1/sqrt(5)*pow(((1-sqrt(5))/2),x);


#define FIBB1(x)  1
#define FIBB2(x)  FIBB1(x-1),1
#define FIBB3(x)  FIBB2(x-1),f(x)
#define FIBB4(x)  FIBB3(x-1),f(x)
#define FIBB5(x)  FIBB4(x-1),f(x)
#define FIBB6(x)  FIBB5(x-1),f(x)
#define FIBB7(x)  FIBB6(x-1),f(x)
#define FIBB8(x)  FIBB7(x-1),f(x)
#define FIBB9(x)  FIBB8(x-1),f(x)
#define FIBB10(x) FIBB9(x-1),f(x)
#define FIBB11(x) FIBB10(x-1),f(x)
#define FIBB12(x) FIBB11(x-1),f(x)
#define FIBB13(x) FIBB12(x-1),f(x)
#define FIBB14(x) FIBB13(x-1),f(x)
#define FIBB15(x) FIBB14(x-1),f(x)
#define FIBB16(x) FIBB15(x-1),f(x)
#define FIBB17(x) FIBB16(x-1),f(x)
#define FIBB18(x) FIBB17(x-1),f(x)
#define FIBB19(x) FIBB18(x-1),f(x)
#define FIBB20(x) FIBB19(x-1),f(x)
// ...
#define FIBB93(x) FIBB92(x-1),f(x)
//#define FIBB94(x) FIBB93(x-1),f(x) //unsigned long long overflow, can't calculate more

#define FIBB(x) FIBB##x(x)

constexpr unsigned long long fib[93] = FIBB(93);

int main()

    // all possible fibbonacci numbers for unsigned long long implementation
    for(int i=0; i<93; ++i)
        std::cout << fib[i] << std::endl;

我认为这是 C++ 内置数组的唯一方式。

【讨论】:

【参考方案3】:

这是一个使用模板参数作为长度的 C++14 解决方案(GCC >= 5.0.0,Clang >= 3.5.0)。您在 constexpr 函数中编写了一个命令式循环(与您的原始帖子相同)。使用disassembler,您可以看到序列作为原始数据嵌入到程序中,即使没有优化 (-O0)。

#include <array>
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>

namespace 
// Create an std::array from a C array (internal) via an
// std::index_sequence.
template <typename T, typename TSequence> struct MakeArrayImpl;
template <typename T, std::size_t... TIndices>
struct MakeArrayImpl<T, std::index_sequence<TIndices...>> 
  static constexpr std::array<T, sizeof...(TIndices)>
  make_array(T values[sizeof...(TIndices)]) 
    return std::array<T, sizeof...(TIndices)>values[TIndices]...;
  
;

// Create an std::array from a C array.
template <typename T, std::size_t TLength>
constexpr std::array<T, TLength> make_array(T values[TLength]) 
  return MakeArrayImpl<T, std::make_index_sequence<TLength>>::make_array(
      values);


// Return an std::array of the first numbers in the Fibonacci sequence.
template <std::size_t TLength>
constexpr std::array<long long int, TLength> fibs() 
  // Original algorithm.
  long long int fib[TLength] = 0;
  fib[0] = 1;
  fib[1] = 1;
  for (std::size_t i = 2; i < TLength; ++i) 
    fib[i] = fib[i - 2] + fib[i - 1];
  
  return make_array<long long int, TLength>(fib);



int main() 
  // Original algorithm.
  const int N = 92;
  long long int fib[N] = 0;
  fib[0] = 1;
  fib[1] = 1;
  for (int i = 2; i < N; ++i)
    fib[i] = fib[i - 2] + fib[i - 1];

  // Test constexpr algorithm against original algorithm.
  static constexpr auto values = fibs<N>();
  static_assert(values.size() == N, "Expected N values in Fibs");
  for (int i = 0; i < N; ++i) 
    if (fib[i] != values[i]) 
      std::cerr << "Mismatch at index " << i << "\n";
      std::cerr << "Expected: " << fib[i] << "\n";
      std::cerr << "Actual  : " << values[i] << "\n";
    
  

【讨论】:

【参考方案4】:

    在您发布的代码示例中,如果使用-O3 优化,编译器很有可能会自行展开循环或至少部分循环。在godbolt 上玩耍,似乎N=100 不会发生这种情况,但会在N 上达到大约40。在这种情况下,它确实发生在编译时,无论它是否是constexpr

    这还指出——在许多机器上,long long int 不足以容纳第 100 个斐波那契数。斐波那契数呈指数增长,您应该预计第 100 个数需要大约 100 位左右。由于整数溢出,您编写的代码在典型机器上会表现出未定义的行为。

使用模板你可以这样做:

// Fibonacci recurrence
template <long int n> 
struct fib_pair 
  typedef fib_pair<n-1> prev;
  static constexpr long int fib_n = prev::fib_n_plus_one;
  static constexpr long int fib_n_plus_one = prev::fib_n + prev::fib_n_plus_one;
;

template <>
struct fib_pair<0> 
  static constexpr long int fib_n = 0;
  static constexpr long int fib_n_plus_one = 1;
;

// List structure
template <long int ... > struct list ;

// Concat metafunction
template <typename A, typename B> struct concat;
template <long int... As, long int... Bs> struct concat<list<As...>, list<Bs...>> 
  typedef list<As..., Bs...> type;
;

// Get a sequence from the fib_pairs
template <long int n>
struct fib_seq 
  typedef typename fib_seq<n-1>::type prev;

  typedef typename concat<prev, list<fib_pair<n>::fib_n>>::type type;
;

template <>
struct fib_seq<0> 
  typedef list<0> type;
;


// Make an array from pack expansion
#include <array>
template <typename T> struct helper;

template <long int ... nums>
struct helper <list<nums...>> 
  typedef std::array<const long int, sizeof...(nums)> array_type;
  static constexpr array_type get_array() 
    return  nums... ;
  
;

// Easy access
template <long int n>
constexpr std::array<const long int, n + 1> get_fib_array() 
  return helper<typename fib_seq<n>::type>::get_array();


#include <iostream>

int main () 
  for (const long int x : get_fib_array<15>()) 
    std::cout << x << std::endl;
  

【讨论】:

是 std::array 实现,不是 T array[N],是吗? 是的,确实如此,但实际上并没有什么不同,我的意思是,如果需要,您可以从中获取T array[N],并且它是在编译时构建的。【参考方案5】:

这是一个 C++11 解决方案,它使用 C++14 库功能 [1](GCC >= 4.9.0,Clang >= 3.5.0)使用模板参数作为长度。您使用递归编写循环。使用disassembler,您可以看到序列作为原始数据嵌入到程序中,即使没有优化 (-O0)。

[1] std::index_sequence 如果在您的标准库中不可用,可以在 C++11 中自行实现。

#include <array>
#include <cstddef>
#include <iostream>
#include <type_traits>
#include <utility>

namespace 
// Create an std::array from a C array (internal) via an
// std::index_sequence.
template <typename T, typename TSequence> struct MakeArrayImpl;
template <typename T, std::size_t... TIndices>
struct MakeArrayImpl<T, std::index_sequence<TIndices...>> 
  static constexpr std::array<T, sizeof...(TIndices)>
  make_array(T values[sizeof...(TIndices)]) 
    return std::array<T, sizeof...(TIndices)>values[TIndices]...;
  
;

// Create an std::array from a C array.
template <typename T, std::size_t TLength>
constexpr std::array<T, TLength> make_array(T values[TLength]) 
  return MakeArrayImpl<T, std::make_index_sequence<TLength>>::make_array(
      values);


// Return an std::array of the first numbers in the Fibonacci sequence.
template <std::size_t TLength>
constexpr std::array<long long int, TLength> fibs() 
  // Original algorithm.
  long long int fib[TLength] = 0;
  fib[0] = 1;
  fib[1] = 1;
  for (std::size_t i = 2; i < TLength; ++i) 
    fib[i] = fib[i - 2] + fib[i - 1];
  
  return make_array<long long int, TLength>(fib);



int main() 
  // Original algorithm.
  const int N = 92;
  long long int fib[N] = 0;
  fib[0] = 1;
  fib[1] = 1;
  for (int i = 2; i < N; ++i)
    fib[i] = fib[i - 2] + fib[i - 1];

  // Test constexpr algorithm against original algorithm.
  static constexpr auto values = fibs<N>();
  static_assert(values.size() == N, "Expected N values in Fibs");
  for (int i = 0; i < N; ++i) 
    if (fib[i] != values[i]) 
      std::cerr << "Mismatch at index " << i << "\n";
      std::cerr << "Expected: " << fib[i] << "\n";
      std::cerr << "Actual  : " << values[i] << "\n";
    
  

【讨论】:

以上是关于是否可以在编译时评估数组?的主要内容,如果未能解决你的问题,请参考以下文章

nameof() 是在编译时评估的吗?

如何判断表达式是在编译时还是运行时评估的?

常量和编译时评估 - 为啥要改变这种行为

是啥阻止了这个 constexpr 函数的编译时评估?

jquery click函数声明在运行时评估变量

C# 逻辑顺序和编译器行为