Home > Enterprise >  When should I be using if constexpr as apposed to a regular if in a constexpr template function?
When should I be using if constexpr as apposed to a regular if in a constexpr template function?

Time:04-14

I've been trying to write a metafuncion to evaluate powers at compile time. I have managed to do it with template metaprogramming, implemented as such:

template<int A, int B>
struct pow {
    static constexpr int value = (B % 2 == 0) ?
                                 pow<A, B / 2>::value * pow<A, B / 2>::value :
                                 A * pow<A, B - 1>::value;
};
template<int A>
struct pow<A, 1> {
    static constexpr int value = A;
};

But, for some reason, why I try to use template functions, this doesn't compile, as it exceeds maximum recursion depth for function definition:

template<int a, int b>
constexpr int template_power() {
    return (b == 0) ?
           1 : ((b % 2 == 0) ?
                template_power<a, b / 2>() * template_power<a, b / 2>() :
                a * template_power<a, b - 1>());
}

What this tells me is that somehow, the conditional statements don't get properly evaluated at compile time, but I don't see any difference between this and the previous implementation.

On the other hand, this constexpr function does compile:

int constexpr power(int a, int b) {
    return (b == 0) ?
           1 : ((b % 2 == 0) ?
                power(a, b / 2) * power(a, b / 2) :
                a * power(a, b - 1));

And, furthermore, if I refactor my template function using if constexpr, it does compile as intended.

template<int a, int b>
int constexpr template_power() {
    if constexpr(b == 0)
        return 1;
    else
        if constexpr(b % 2 == 0)
            return template_power<a,b/2>() * template_power<a, b/2>();
    else
        return a * template_power<a, b - 1>();
}

How do I make sense of all of this?

My guess is that for the function template, the conditional statement evaluates all the branches before deciding what to choose, which causes it to generate unaccounted for functions (such as when b is negative). If that is the case, why do the conditional statements get properly evaluated for the template struct and the constexpr function, but not the template function?

The verbatim error message is this:

[path]/main.cpp: In instantiation of 'constexpr int template_power() [with int a = 2; int b = -896]':
[path]/main.cpp:42:45:   recursively required from 'constexpr int template_power() [with int a = 2; int b = -1]'
[path]/main.cpp:41:41:   recursively required from 'constexpr int template_power() [with int a = 2; int b = 2]'
[path]/main.cpp:41:41:   required from 'constexpr int template_power() [with int a = 2; int b = 5]'
[path]/main.cpp:47:43:   required from here
[path]/main.cpp:41:41: fatal error: template instantiation depth exceeds maximum of 900 (use '-ftemplate-depth=' to increase the maximum)
   41 |                 template_power<a, b / 2>() * template_power<a, b / 2>() :

CodePudding user response:

  • In your first example, since the ternary operator is evaluated at runtime (just like a regular if statement), it cannot stop the instantiation of the template, and since your template has no specialization for the exit, all branches will be instantiated, which makes it recursive infinitely.

  • In your second example, since there is no template involved, there is no instantiation of the template and it compiles.

  • In your third example, since if constexpr only instantiates the template when the expression evaluates to true, and the other branches will be discarded, it compiles fine as well.

  • Related