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;
}
gonidelis
  • 885
  • 10
  • 32
Exxul
  • 508
  • 5
  • 10
  • 3
    Possible duplicate of [Replacement for ternary operator in template metaprogramming](https://stackoverflow.com/questions/45378031/replacement-for-ternary-operator-in-template-metaprogramming) – L. F. Nov 25 '19 at 15:11
  • 2
    The goal was to used template meta programming. ```constexpr``` is not an option. – Exxul Nov 25 '19 at 15:26
  • 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. – MSalters Nov 26 '19 at 16:55
  • Sorry I removed the tag. – Exxul Nov 27 '19 at 19:32

4 Answers4

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;
HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
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

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
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;
};
Evg
  • 25,259
  • 5
  • 41
  • 83
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.

Paul Evans
  • 27,315
  • 3
  • 37
  • 54