计算编译时数组时编译器相关的错误

时间:2016-07-22 17:13:12

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

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

#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无法计算它?

2 个答案:

答案 0 :(得分:3)

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

  

为什么array [0]在两种情况下的值均为0,尽管设置为1 ......?

因为您希望设置为1的value在基类Factorial<N,0>中声明,但被(基本案例模板)派生类中的value成员隐藏。

顺便说一句,如果您喜欢使用这样的直接方法计算阶乘编译时间,那么您将能够使用C ++ 17执行此操作:

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

答案 1 :(得分:1)

我不知道您的任何一个问题的答案,但我知道如何修复您的代码,以便它可以在我可以方便地测试的所有编译器中工作,基于this old answer和{的技术{3}}。我没有用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;
}
为了简化概念,我将factorial的计算与数组的汇编分开。这意味着它需要在编译时进行O(N 2 )计算,除非编译器足够聪明地记忆Factorial<I>::value,它可能就是这样。

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

for (auto x : f)适用于FactorialArray;上面显示的更复杂的循环是为了证明索引正确。

请注意,FactorialArray<10, 42> f;会在没有投诉的情况下编译,并会错误地报告11! = 42.在一个供公众使用的库中,可能值得在FactorialArray命名空间中将递归detail带走,然后将其包装在一个不会带有可变参数的公共模板中:< / p>

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

为读者练习:改变它来计算this other old answer,从而展示模板元编程的图灵完备性以及如何构建二维数组。