【问题标题】:Compiler dependent error when computing compile-time array计算编译时数组时的编译器相关错误
【发布时间】:2016-11-26 16:27:54
【问题描述】:

我的目标是在编译时计算阶乘数组,而不创建任何类对象或调用静态函数。这是最少的代码:

#include <iostream>
#include <cinttypes>
#include <array>

namespace CompileTime
{
    enum {MaxFactorial = 10};

    template<size_t N, size_t I = N-1>
    class Factorial : public Factorial<N, I-1>
    {
    public:
        static const uint64_t value;
    };


    template<size_t N>
    class Factorial<N,1> : public Factorial<N, 0>
    {
    public:
        static const uint64_t value;
    };

    template<size_t N>
    class Factorial<N,0>
    {
    public:
        static const uint64_t value;
        static std::array<uint64_t,N> array;
    };


    template<size_t N>
    const size_t Factorial<N,1>::value = Factorial<N,0>::array[1] = 1;

    template<size_t N>
    const size_t Factorial<N,0>::value = Factorial<N,0>::array[0] = 1;

    template <size_t N, size_t I>
    const size_t Factorial<N,I>::value = Factorial<N,0>::array[I] =
                                  I * Factorial<N, I-1>::value;

    template <size_t N>
    std::array<uint64_t,N> Factorial<N, 0>::array;

    template class Factorial<MaxFactorial>;

    typedef Factorial<MaxFactorial> PrecomputerFactorial;
}

int main()
{
    using CompileTime::PrecomputerFactorial;

    for(auto x : PrecomputerFactorial::array)
        std::cout << x << std::endl;
    std::cout << std::endl;
}

用 GCC 5.3.0 编译给出程序输出:

0
1
2
6
24
120
720
5040
40320
362880

在 MSVC 2015 中:

0
1
0
0
0
0
0
0
0
0

我有两个问题: 首先,为什么array[0] 在这两种情况下都具有0 的值,尽管这里设置为1

template<size_t N>
    const size_t Factorial<N,0>::value = Factorial<N,0>::array[0] = 1;

二、为什么MSVC 2015计算失败?

【问题讨论】:

  • 观察没有回答任何一个问题:使用 GCC 6,在静态构造时计算阶乘并初始化数组,not 在编译时。我想你可能需要在周围撒一些constexprs。

标签: c++ templates c++11 compile-time


【解决方案1】:

一个基本问题是您使用数组下标运算符 (array[0]) 作为 constexpr 操作。但在 C++17 之前它不是 constexpr: http://en.cppreference.com/w/cpp/container/array/operator_at

为什么 array[0] 在这两种情况下都有值 0,尽管被设置为 1 ...?

因为您打算设置为 1 的 value 已在基类 Factorial&lt;N,0&gt; 中声明,但在(基本案例模板)派生类中被 value 成员隐藏。

顺便说一句,如果你喜欢用这样的直接方法计算阶乘编译时间,你可以用 C++17 做到这一点:

template<size_t N>
constexpr auto factorial(){
   if constexpr(N<2){
     return 1;
   }
   return N*factorial<N-1>();
}

【讨论】:

  • 我仍然不清楚为什么Factorial&lt;N,0&gt; 中的array[0]value 不会设置为“1”。我的意思是,它在定义的那一刻就被初始化了,对吧?这是基类成员初始化,不是派生的,对吧?
【解决方案2】:

我不知道你的任何一个问题的答案,但我知道如何修复你的代码,以便它可以在我可以方便地测试的所有编译器中工作,基于this old answerthis other old answer 的技术。我没有用 MSVC 测试过,很想知道它是否有效。

#include <iostream>
#include <cinttypes>
#include <array>

using std::uint64_t;

// Helper template that computes the factorial of one integer
template<uint64_t I> struct Factorial
{ static constexpr uint64_t value = I * Factorial<I-1>::value; };

template<> struct Factorial<0> { static constexpr uint64_t value = 1; };

// FactorialArray recursively assembles the desired array as a variadic
// template argument pack from a series of invocations of Factorial
template<uint64_t I, uint64_t... Values> struct FactorialArray
  : FactorialArray<I-1, Factorial<I>::value, Values...>
{};

// and in the base case, initializes a std::array with that pack
template<uint64_t... Values> struct FactorialArray<uint64_t(-1), Values...>
  : std::array<uint64_t, sizeof...(Values)>
{
  constexpr FactorialArray()
    : std::array<uint64_t, sizeof...(Values)> ({{Values...}})
  {}
};

int main()
{
  static FactorialArray<10> f;
  for (std::size_t i = 0; i < f.size(); i++)
    std::cout << i << "! = " << f[i] << '\n';
  return 0;
}

为了概念上的简单,我将阶乘的计算与数组的组装分开。这意味着它需要在编译时进行 O(N2) 计算,除非编译器足够聪明地记住 Factorial&lt;I&gt;::value,它很可能就是这样。

比我更熟练的 C++11 特性的人可能能够简化这一点,特别是 FactorialArray 的基本情况,这是非常货物崇拜 + 改变事物 - 直到 - 编译器停止 -抱怨我。

for (auto x : f) 可以与FactorialArray 一起使用;上面显示的更复杂的循环是为了证明索引是正确的。

请注意,FactorialArray&lt;10, 42&gt; f; 将毫无怨言地编译,并且会错误地报告 11! = 42. 在公共消费的库中,可能值得将递归 FactorialArray 隐藏在 detail 命名空间中,然后将其包装在不接受可变参数的公共模板中:

namespace detail {
    // Factorial and FactorialArray as above
}
template<uint64_t I> struct FactorialArray : detail::FactorialArray<I> {};

读者练习:将其更改为计算two-argument Ackermann–Péter function,从而演示模板元编程的图灵完备性以及如何构造二维数组。

【讨论】:

  • 是否使用 GCC 5.3.0 和 MSVC 2015 进行编译,除了不生成 0!。正确的序列应该是1,1,2...,但它省略了第一个元素(但我看到Factorial&lt;0&gt; 是专门的,嗯)
  • 我自己也注意到了。这是一个愚蠢的错误——FactorialArray 的基本情况应该是 -1,而不是 0。很快就会修复。
  • 简洁明了 :) 但我想知道一件事 - FactorialArray 的第一个非模板参数是 uint64_t,然后用 -1 专门化它是否安全,这将是 @987654340 @?
  • 请注意最新版本中添加的显式转换,没有它,clang(但不是 gcc)会抱怨“无效的缩小转换”。不过,值 (uint64_t(-1) == UINT64_MAX) 和从 uint64_t(0) 中减 1 的效果(无符号数的算术运算总是回绕)都是明确定义的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-19
  • 1970-01-01
  • 2011-02-20
  • 2011-01-29
  • 1970-01-01
  • 2012-01-28
相关资源
最近更新 更多