Modèle de méta-programmation

38

Quelqu'un peut-il m'expliquer pourquoi la première méthode de méta-programmation de modèle va en boucle infinie, mais la seconde fonctionne correctement.

#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;
}
Exxul
la source
2
L'objectif était d'utiliser une méta-programmation de modèle. constexprn'est pas une option.
Exxul
Ajout d'une balise c ++ 98 pour rendre explicite ce constexprn'est pas une option. (Il a été introduit en C ++ 11). Cela invalide les réponses existantes. Exxul, veuillez préciser à quelle version C ++ vous êtes limité.
MSalters
Désolé d'avoir supprimé la balise.
Exxul

Réponses:

44
(N<M) ? commondivs<N,(M-N)>::val : commondivs<(N-M),M>::val

Cette ligne provoque l'instanciation des deux commondivs<N,(M-N)>::valet commondivs<(N-M),M>::val, même si la condition est connue au moment de la compilation et qu'une des branches ne sera jamais prise.

Remplacez ? :par std::conditional_t, qui n'a pas cette limitation:

static const int val = std::conditional_t<N < M, commondivs<N,(M-N)>, commondivs<(N-M),M>>::val;
HolyBlackCat
la source
15

Le problème est que tous les opérandes de l'opérateur conditionnel seront évalués, donc les deux commondivs<N,(M-N)> et commondivs<(N-M),M>obtenir instanciés et leur composants sont valévalués, puis conduit à l' instanciation de modèle récursif.

Vous pouvez appliquer constexpr si et le placer dans une constexpr staticfonction membre.

Si la valeur est true, alors la déclaration-faux est rejetée (si elle est présente), sinon, la déclaration-vraie est rejetée.

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();
};

VIVRE

songyuanyao
la source
Évalué ou simplement instancié?
Daniel McLaury
@DanielMcLaury Evaluated; pas seulement instancié.
songyuanyao
La valeur de ::val doit être générée sur les deux branches bien sûr, mais il s'agit toujours d'une instanciation (d'un modèle avec un membre const statique). L'évaluation au moment de l'exécution ne se produit pas ... eh bien, ce n'est évidemment pas possible car elle ne compile jamais ...
Inutile
8

L'opérateur ternaire n'est pas comme if constexpr: quand un compilateur le voit, il doit générer du code pour les deux branches. En d'autres termes, pour instancier un modèle commondivs<M, N>, un compilateur instancie à la fois les modèles commondivs<N, M - N>et commondivs<N - M, M>.

Contrairement à cela, commondiv(N, M - N)et commondiv(N - M, M)sont traduits en deux appels de fonction. Laquelle est prise, sera décidée lorsque la fonction sera effectivement appelée.

Une addition.

HolyBlackCat a donné une solution avec std::conditional_t. En voici un autre:

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
la source
0

Vous obtenez une récursion infinie car:

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

n'est pas du tout un programme de méta-modèle parce que ?: , comme le dit @Eng, ce n'est pasconstexpr .

Vous voulez regarder la réponse de @ HolyBlackCat.

Paul Evans
la source
1
Ça n'aidera pas. ?:ne l'est pas constexpr.
Evg
Non je l'essaye. La même boucle infinie.
Exxul