0

I am trying to play with template metaprogramming, constexpr and if constexpr and have come up with 3 different ways of doing a N-recursive / N-factorial operation.

All three examples are some I've found here on SO or by searching on the net - and then modified it, so they do the same

The first example is using template metaprogramming: example 1

template<int N>
struct NGenerator
{
    static const int result = N + NGenerator<N-1>::result;
};

template<>
struct NGenerator<0>
{
    static const int result = 1; 
};

static int example1 = NGenerator<5>::result;

The second one is still using a template, but I've thrown a constexpr in: example 2

template<int N>
constexpr int example2() 
{
return N + example2<N - 1>();
}


template<>
constexpr int example2<0>() 
{ 
    return 1;
}

static int ex2 = example2<5>();

The third one, is where I've removed the template and "only" use constexpr: example 3

constexpr int generator(int n)
{
    return (n <= 1) ? 1 : n + generator(n - 1);
}

static int ex3 = generator(5);

In my opinion all three do the same - when the input number is a compile time constant. All three are recursive, all three work compile-time.

My question is - what is the difference between the three? Which one is most preferable?

And lastly - I would like to implement the "if constexpr" but haven't been able to, so my workaround has been to do the "if-statement" in example 3 - which isn't really a true if-statement, but the closest I could get to a compile-time if - if it is in any way the same, which I am not sure of.

nelion
  • 1,712
  • 4
  • 17
  • 37
  • "My question is - what is the difference between the three?" - Have you checked your compilers output for an optimised build? That should answer the question definitively. – Jesper Juhl Dec 29 '19 at 18:40
  • Hi @JesperJuhl - sorry for asking, but how do I do that? – nelion Dec 29 '19 at 18:47
  • The only difference is probably how fast it compiles. Also, I suggest a different approach: use a plain loop. They're allowed in constexpr functions in modern C++. – HolyBlackCat Dec 29 '19 at 18:49
  • You'd do that by either telling your compiler to stop after generating assembly and just dump that ASM to a file (all major compilers have options to do that) or you could disassemble the generated executable and look at the final generated code. Tools like `nm` and `objdump` may be useful. – Jesper Juhl Dec 29 '19 at 18:50
  • 1
    You could use [godbolt.org](https://godbolt.org/) for that. – t.niese Dec 29 '19 at 18:52
  • 1
    Unrelated, but the first two examples return 16, whereas the third one returns 15! – Olaf Dietsche Dec 29 '19 at 19:07
  • 1
    @OlafDietsche: those are indeed not *"N-factorial"* `+` instead of `*`. – Jarod42 Dec 29 '19 at 19:24
  • @t.niese thanks I am trying to use that now - but throwing all three examples in one by one the compiler tells me "ASM generation compiler returned: 0" and "no assembly generated".. – nelion Dec 29 '19 at 19:40

2 Answers2

2

With toy examples like this there won't be much of a difference. With more complex examples, recursove template instantiation practically has memory costs proportional to the sum of the lengths of all of the templates instantiated names, including arguments. This is really easy to blow up.

In my experience constexpr functions tend to compile faster.

All 3 will be likely memoized by the compiler; but the constexpr non-template function will spew way fewer symbols (noise).

Better than all of these would be a constexpr function with a loop.

Compilers are free to do most of the options at runtime as you did not insist on compile time evaluation. Replace static int with constexpr int.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • So every time possible - constexpr > template? I didn't think about the last part - that the compilers are free to do them at runtime, because of the evaluation. I am using static int, so that I can increment it every time it is called. When I use constexpr int, it tells me that the "expression must be a modifiable lvalue".. – nelion Dec 29 '19 at 19:38
1

Example 1 is guaranteed to be done at compile time.

constexpr functions (example2 and example3) can be called in non constexpr context and so be computed at runtime (compiler might still optimize it to compute it at compile time withas-if rule).

I would do something like:

constexpr std::size_t factorial(std::size_t n)
{
    std::size_t res = 1;
    for (std::size_t i = 1; i < n; ++i) {
        res *= i;
    }
    return res;
}

template <std::size_t N>
static constexpr std::size_t Factorial = factorial(5);

static std::size_t ex4 = Factorial<5>;

C++20 adds consteval to only allow constexpr evaluation.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • thanks for the example, appreciate it! But what is exactly the difference between your example and mine number 3? Also I didn't think you could run a regular for-loop as compile-time. Would it be possible to do a "if constexpr" in your example? – nelion Dec 29 '19 at 21:59
  • 1
    In C++11, constexpr functions are very limited. since C++14, you might do much more (as for loop). The big difference with your is that I use it afterward in constant expression. `std::cout << factorial(5)` would be a runtime call and not a compile time call (unless optimized by compiler). whereas `constexpr auto fact5 = factorial(5); std::cout << fact5;` would do computation of `fact5` at compile time. – Jarod42 Dec 30 '19 at 07:19