Meta programación de plantillas

38

¿Alguien puede explicarme por qué la primera plantilla de metaprogramación va a un ciclo infinito, pero la segunda se ejecuta correctamente?

#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
fuente
2
El objetivo era utilizar la meta programación de plantillas. constexprno es una opinión.
Exxul
Se agregó la etiqueta c ++ 98 para hacer explícito que constexprno es una opción. (Fue introducido en C ++ 11). Eso invalida las respuestas existentes. Exxul, aclare a qué versión de C ++ está limitado.
MSalters
Lo siento, eliminé la etiqueta.
Exxul

Respuestas:

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

Esta línea provoca la creación de instancias de ambos commondivs<N,(M-N)>::vale commondivs<(N-M),M>::val, incluso si la condición se conoce en el momento de la compilación y nunca se tomará una de las ramas.

Reemplace ? :con std::conditional_t, que no tiene esta limitación:

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

El problema es que se evaluarán todos los operandos de operador condicional, por lo tanto commondivs<N,(M-N)>, y commondivs<(N-M),M>consiguen crear instancias y su valget evaluados y luego conduce a instancias de plantilla recursiva.

Puede aplicar constexpr if y ponerlo en una constexpr staticfunción miembro.

Si el valor es true, entonces se descarta el enunciado falso (si está presente); de lo contrario, se descarta el enunciado verdadero.

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

EN VIVO

songyuanyao
fuente
¿Evaluado o solo instanciado?
Daniel McLaury
@DanielMcLaury Evaluado; no solo instanciado.
songyuanyao
El valor de ::valtiene que ser generado en ambas ramas seguro, pero esto todavía es una instanciación (de una plantilla con un miembro constante estático). La evaluación en tiempo de ejecución no ocurre ... bueno, obviamente no puede, ya que nunca se compila ...
Inútil el
8

El operador ternario no es como if constexpr: cuando un compilador lo ve, tiene que generar código para ambas ramas. En otras palabras, para crear una instancia de una plantilla commondivs<M, N>, un compilador crea una instancia de ambas plantillas commondivs<N, M - N>y commondivs<N - M, M>.

En contraste con eso, commondiv(N, M - N)y commondiv(N - M, M)se traducen en dos llamadas a funciones. Cuál se toma, se decidirá cuando se llame realmente a la función.

Adición.

HolyBlackCat dio una solución con std::conditional_t. Aquí hay otro:

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
fuente
0

Obtienes una recursión infinita porque:

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

no es la programación metatemplate en absoluto porque ?: , como dice @Eng, no lo es constexpr.

Desea ver la respuesta de @ HolyBlackCat.

Paul Evans
fuente
1
No va a ayudar ?:no es constexpr.
Evg
No, lo intento. El mismo bucle infinito.
Exxul