Gracias, no necesito ningún libro para enseñarme lo que significa constexpr. Estoy enseñando constexpr y mi ejemplo simple no logra convencer a los estudiantes de por qué deberían usar la ventaja del cálculo del tiempo de compilación mediante constexpr.

Además, evite estrictamente los enlaces a preguntas como this que no tiene código de ensamblaje o perfiles y no tienen sentido para mi pregunta.


Estoy buscando un ejemplo para mostrar por qué constexpr es útil en absoluto y no se puede descartar.

Bueno, en muchos casos si constexpr se reemplaza por const, no sucede nada malo. Entonces, diseñé los siguientes ejemplos:

Main_const.cpp

#include <iostream>
using namespace std;

const int factorial(int N)
{
    if(N<=1)
        return 1;
    else 
        return N*factorial(N-1);
}

int main()
{
    cout<<factorial(10)<<endl;
    return 0;
}

Y

Main_constexpr.cpp

#include <iostream>
using namespace std;

constexpr int factorial(int N)
{
    if(N<=1)
        return 1;
    else 
        return N*factorial(N-1);
}

int main()
{
    cout<<factorial(10)<<endl;
    return 0;
}

Pero el problema es que para ellos, el código ensamblador es

Main_const.asm

12:main_last.cpp **** int main()
13:main_last.cpp **** {
132                     .loc 1 13 0
133                     .cfi_startproc
134 0000 4883EC08       subq    $8, %rsp
135                     .cfi_def_cfa_offset 16
14:main_last.cpp ****   cout<<factorial(10)<<endl;
136                     .loc 1 14 0
137 0004 BE005F37       movl    $3628800, %esi
137      00
138 0009 BF000000       movl    $_ZSt4cout, %edi
138      00
139 000e E8000000       call    _ZNSolsEi

Y para el último es

Main_constexpr.asm

12:main_now.cpp  **** int main()
13:main_now.cpp  **** {
11                      .loc 1 13 0
12                      .cfi_startproc
13 0000 4883EC08        subq    $8, %rsp
14                      .cfi_def_cfa_offset 16
14:main_now.cpp  ****   cout<<factorial(10)<<endl;
15                      .loc 1 14 0
16 0004 BE005F37        movl    $3628800, %esi
16      00
17 0009 BF000000        movl    $_ZSt4cout, %edi
17      00
18 000e E8000000        call    _ZNSolsEi
18      00

Lo que significa que el compilador obviamente ha realizado un plegado constante de (10!) = 3628800 para ambos casos usando cosnt o constexpr.

La compilación se realiza a través de

g++ -O3 -std=c++17 -Wa,-adhln -g main.cpp>main.asm

A pesar de que en la mayoría de los casos, mucha gente asume que el código ahora se ejecuta más rápido sin ninguna investigación, considerando el hecho de que los compiladores son inteligentes, me pregunto si hay algún beneficio de optimización real, honesto y significativo detrás de constexpr.

-6
ar2015 19 feb. 2018 a las 08:18

2 respuestas

La mejor respuesta

Con el único propósito de optimización, es imposible construir una constexpr secuencia de llamada de expresión / función para la cual es imposible que un compilador optimice una Equivalente a constexpr. Por supuesto, esto se debe a que constexpr tiene varios requisitos para su uso. Cualquier código constexpr debe estar insertado y ser visible para el compilador de esa unidad de traducción. Recursivamente, a través de todas las expresiones que conducen a la generación de un valor constexpr.

De manera similar, a las funciones constexpr no se les permite hacer cosas como asignar memoria (todavía), manipular el puntero de funciones de bajo nivel (todavía), llamar a funciones que no sean constexpr y otras cosas similares que evitarían que el compilador pueda ejecutarlos en tiempo de compilación.

Como tal, si tiene una construcción constexpr, una versión equivalente que no sea constexpr tendría todas estas mismas propiedades de su implementación. Y dado que el compilador debe poder ejecutar código constexpr en tiempo de compilación, tendría que ser al menos teóricamente capaz de hacer lo mismo con el equivalente no constexpr.

Si lo optimiza o no en un caso particular es irrelevante; esas cosas cambian con cada versión del compilador. El hecho de que pudiera es suficiente.

Su problema es que cree que el propósito principal de constexpr es el rendimiento. Que es una optimización hecha para permitir cosas que no podría hacer de otra manera.

La función de constexpr en el rendimiento es principalmente que, al etiquetar una función o variable como constexpr, el compilador le impide hacer cosas que las implementaciones podrían no ejecutar en tiempo de compilación. Si desea la ejecución en tiempo de compilación en todas las plataformas, debe permanecer dentro de los límites definidos por el estándar de ejecución en tiempo de compilación.

La sintaxis significa que el compilador evita activamente que usted haga cosas que no sean constexpr. No puede escribir código accidentalmente que no se pueda ejecutar en tiempo de compilación.

Es decir, la pregunta que debería tener en cuenta no es si el código constexpr podría escribirse de la misma manera sin él. Es si habría escrito el código de una manera constexpr sin la palabra clave. Y para cualquier sistema de complejidad, la respuesta se acerca cada vez más a "no", aunque sólo sea por el hecho de que es fácil hacer accidentalmente algo que el compilador no puede ejecutar en tiempo de compilación.

9
Nicol Bolas 21 dic. 2018 a las 16:21

Tienes que usarlo en una expresión constexpr para forzar la evaluación del tiempo de compilación:

int main()
{
    constexpr int fact_10 = factorial(10); // 3628800
    std::cout << fact_10 << std::endl;
    return 0;
}

De lo contrario, confía en la optimización del compilador.

Además, constexpr permite su uso, mientras que const simple no se permitirá:

Entonces asumiendo:

constexpr int const_expr_factorial(int) {/*..*/}
int factorial(int) {/*..*/}

Tienes:

char buffer[const_expr_factorial(5)]; // OK
char buffer[factorial(5)]; // KO, might compile due to VLA extension

std::integral_constant<int, const_expr_factorial(10)> fact_10; // OK
std::integral_constant<int, factorial(10)> fact_10; // KO
1
Jarod42 19 feb. 2018 a las 09:30