1

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?

xinaiz
  • 7,744
  • 6
  • 34
  • 78
  • Observation which doesn't answer either question: with GCC 6, the factorial is computed and the array initialized at static construction time, _not_ at compile time. I think you may need to sprinkle some `constexpr`s around. – zwol Jul 22 '16 at 17:20

2 Answers2

3

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>();
}
Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • It's still not clear to me why `array[0]` and `value` of `Factorial` would be not set to '1'. I mean, it's initialized the moment it's defined, right? It's base class member initialization, not derived one, right? – xinaiz Jul 22 '16 at 17:57
1

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.

Community
  • 1
  • 1
zwol
  • 135,547
  • 38
  • 252
  • 361
  • Does the job compiling with both GCC 5.3.0 and MSVC 2015, except not generating `0!`. The right sequence should be `1,1,2...`, but it omits first element (yet I see that `Factorial<0>` is specialized, hmmm) – xinaiz Jul 22 '16 at 18:03
  • I just noticed that myself. It's a silly mistake -- the base case for `FactorialArray` should be -1, not 0. Fixed shortly. – zwol Jul 22 '16 at 18:04
  • Neat and clear :) But I wonder about one thing - first non-template argument of `FactorialArray` is `uint64_t`, then is it safe to specialize it with `-1` which would be `std::numeric_limits::max();`? – xinaiz Jul 22 '16 at 18:15
  • Note the explicit cast added in the most recent revision, without which clang (but not gcc) complains about an "invalid narrowing conversion". Both the value (`uint64_t(-1) == UINT64_MAX`) and the effect of subtracting 1 from `uint64_t(0)` (arithmetic on unsigned quantities always wraps) are well-defined, though. – zwol Jul 22 '16 at 18:30