Recursion using template meta programming
Asked Answered
M

4

39

Can somebody explain to me, why the first call using the template function is falling into an infinite loop, while the second compile-time function runs correctly?

#include <iostream>
using namespace std;

template<int N, int M>
struct commondivs {                                              
  static const int val = (N<M) ? commondivs<N,(M-N)>::val : commondivs<(N-M),M>::val;
};

template<int N>
struct commondivs<N,N> {
  static const int val = N;
};


int commondiv(int N, int M){
    if(N==M){
        return N;
    }   
    return (N<M)?commondiv(N,(M-N)):commondiv((N-M),M);     
}

int main() {

    cout << commondivs<9,6>::val << endl;
    cout << commondiv(9,6) << endl;
    return 0;
}
Mull answered 25/11, 2019 at 15:4 Comment(4)
Possible duplicate of Replacement for ternary operator in template metaprogrammingRaskind
The goal was to used template meta programming. constexpr is not an option.Mull
Added c++98 tag to make explicit that constexpr is not an option. (It was introduced in C++11). That does invalidate existing answers. Exxul, please clarify which C++ version you're limited to.Wellspring
Sorry I removed the tag.Mull
F
45
(N<M) ? commondivs<N,(M-N)>::val : commondivs<(N-M),M>::val

This line causes instantiation of both commondivs<N,(M-N)>::val and commondivs<(N-M),M>::val, even if the condition is known at compile time and one of the branches will never be taken.

Replace ? : with std::conditional_t, which doesn't have this limitation:

static const int val = std::conditional_t<N < M, commondivs<N,(M-N)>, commondivs<(N-M),M>>::val;
Fluky answered 25/11, 2019 at 15:27 Comment(0)
C
16

The problem is all the operands of conditional operator will be evaluated, so both commondivs<N,(M-N)> and commondivs<(N-M),M> get instantiated and their val get evaluated and then leads to recursive template instantiation.

You can apply constexpr if and put it in a constexpr static member function.

If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

template<int N, int M>
struct commondivs {                                              
  constexpr static int get_val() {
    if constexpr (N<M) return commondivs<N,(M-N)>::val; // if true, the else part won't be evaluated
    else return commondivs<(N-M),M>::val;               // vice versa
  }
  static const int val = get_val();
};

LIVE

Chrystel answered 25/11, 2019 at 15:20 Comment(3)
Evaluated or just instantiated?Carriecarrier
@DanielMcLaury Evaluated; not just instantiated.Chrystel
The value of ::val has to be generated on both branches sure, but this is still instantiation (of a template with a static const member). Evaluation at run time does not happen ... well, it obviously can't since it never compiles ...Teutonism
L
8

The ternary operator is not like if constexpr: when a compiler sees it, it has to generate code for both branches. In other words, to instantiate a template commondivs<M, N>, a compiler instantiates both templates commondivs<N, M - N> and commondivs<N - M, M>.

In contrast to that, commondiv(N, M - N) and commondiv(N - M, M) are translated into two function calls. Which one is taken, will be decided when the function is actually called.

Addition.

HolyBlackCat gave a solution with std::conditional_t. Here is another one:

template<int N, int M>
struct commondivs {                                              
    static constexpr int min = (N < M) ? N : M;
    static constexpr int max = (N < M) ? M : N;
    static constexpr int val = commondivs<min, max - min>::val;
};

template<int N>
struct commondivs<N, N> {
    static constexpr int val = N;
};
Litta answered 25/11, 2019 at 15:21 Comment(0)
H
0

You get infinite recursion because:

static const int val = (N<M) ? commondivs<N,(M-N)>::val : commondivs<(N-M),M>::val;

isn't metatemplate programming at all because ?:, as @Eng says, isn't constexpr.

You want to look at @HolyBlackCat's answer.

Horsemanship answered 25/11, 2019 at 15:16 Comment(2)
It won't help. ?: is not constexpr.Litta
No I try it. The same infinite loop.Mull

© 2022 - 2024 — McMap. All rights reserved.