xinaiz
xinaiz

Reputation: 7788

Compiler dependent error when computing compile-time array

My goal is to compute array of factorials at compile time without creating any class objects or calling static functions. Here is minimal code:

#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;
}

Compiling with GCC 5.3.0 gives program output:

0
1
2
6
24
120
720
5040
40320
362880

And with MSVC 2015:

0
1
0
0
0
0
0
0
0
0

I have two questions: First, why array[0] has value 0 in both cases, despite being set to 1 here:

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

Second, why MSVC 2015 fails to compute it?

Upvotes: 1

Views: 209

Answers (2)

Johan Lundberg
Johan Lundberg

Reputation: 27028

A fundamental problem is that you use array subscript operator (array[0]) as a constexpr operation. But it is not constexpr until C++17: http://en.cppreference.com/w/cpp/container/array/operator_at

Why array[0] has value 0 in both cases, despite being set to 1 ... ?

Because the value you aim to set to 1 is declared in the base class Factorial<N,0> but is hidden by the value member in the (base case template) derived class.

By the way, if you do like to calculate factorials compile time with a direct method like this, you'll be able to do this with C++17:

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

Upvotes: 3

zwol
zwol

Reputation: 140609

I don't know the answer to either of your questions, but I do know how to fix your code so it works in all the compilers I can conveniently test, based on techniques from this old answer and this other old answer. I have not tested this with MSVC and am curious to know whether it works.

#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;
}

I separated computation of the factorial from assembly of the array for conceptual simplicity. This means that it requires O(N2) computation at compile time, unless the compiler is clever enough to memoize Factorial<I>::value, which it may well be.

Someone more skilled than I with C++11 features might be able to simplify this, particularly the base case of FactorialArray, which is very much cargo-cult + change-things-until-the-compiler-stops-complaining on my part.

for (auto x : f) does work with FactorialArray; the more complicated loop shown above is to demonstrate that the indices come out right.

Note that FactorialArray<10, 42> f; will compile without complaint and will erroneously report that 11! = 42. In a library for public consumption, it might be worth squirrelling the recursive FactorialArray away in a detail namespace, then wrapping it in a public template that doesn't take variadic arguments:

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

Exercise for the reader: change this to compute the two-argument Ackermann–Péter function, thus demonstrating both the Turing-completeness of template metaprogramming and how to construct two-dimensional arrays.

Upvotes: 1

Related Questions