¿Tiene sentido la variable constexpr estática dentro de una función?

193

Si tengo una variable dentro de una función (por ejemplo, una gran matriz), ¿tiene sentido declarar ambas staticy constexpr? constexprgarantiza que la matriz se crea en tiempo de compilación, entonces, ¿ staticsería inútil?

void f() {
    static constexpr int x [] = {
        // a few thousand elements
    };
    // do something with the array
}

¿ staticRealmente está haciendo algo allí en términos de código generado o semántica?

David Stone
fuente

Respuestas:

231

La respuesta corta es que no solo es staticútil, sino que siempre va a ser deseable.

Primero, tenga en cuenta que staticy constexprson completamente independientes entre sí. staticdefine la vida útil del objeto durante la ejecución; constexprespecifica que el objeto debe estar disponible durante la compilación. La compilación y la ejecución son disjuntas y no contiguas, tanto en el tiempo como en el espacio. Entonces, una vez que se compila el programa, constexprya no es relevante.

Cada variable declarada constexpres implícita const, pero consty staticson casi ortogonal (a excepción de la interacción con static constnúmeros enteros).

El C++modelo de objetos (§1.9) requiere que todos los objetos que no sean campos de bits ocupen al menos un byte de memoria y tengan direcciones; además, todos esos objetos observables en un programa en un momento dado deben tener direcciones distintas (párrafo 6). Esto no requiere que el compilador cree una nueva matriz en la pila para cada invocación de una función con una matriz const local no estática, porque el compilador podría refugiarse en el as-ifprincipio siempre que pueda probar que ningún otro objeto puede ser observado.

Desafortunadamente, eso no será fácil de probar, a menos que la función sea trivial (por ejemplo, no llama a ninguna otra función cuyo cuerpo no sea visible dentro de la unidad de traducción) porque las matrices, más o menos por definición, son direcciones. Por lo tanto, en la mayoría de los casos, la const(expr)matriz no estática tendrá que recrearse en la pila en cada invocación, lo que frustra el punto de poder calcularla en el momento de la compilación.

Por otro lado, un static constobjeto local es compartido por todos los observadores, y además puede inicializarse incluso si la función en la que se define nunca se llama. Por lo tanto, nada de lo anterior se aplica, y un compilador es libre no solo para generar solo una sola instancia; es libre de generar una sola instancia en el almacenamiento de solo lectura.

Así que definitivamente deberías usarlo static constexpren tu ejemplo.

Sin embargo, hay un caso en el que no querrás usar static constexpr. A menos que un constexprobjeto declarado sea utilizado o declarado por ODRstatic , el compilador es libre de no incluirlo en absoluto. Eso es bastante útil, porque permite el uso de constexprmatrices temporales en tiempo de compilación sin contaminar el programa compilado con bytes innecesarios. En ese caso, claramente no querrá usar static, ya que statices probable que obligue al objeto a existir en tiempo de ejecución.

rici
fuente
2
@AndrewLazarus, no puedes lanzar lejos constde un constobjeto, solo desde un punto const X*que apunta a un X. Pero ese no es el punto; El punto es que los objetos automáticos no pueden tener direcciones estáticas. Como ya he dicho, constexprdeja de ser significativa una vez finalizada la compilación, por lo que no hay nada de esparcir (y, posiblemente, nada en absoluto, porque el objeto no está garantizada incluso de existir en tiempo de ejecución.)
RICI
17
Siento que no solo esta respuesta es increíblemente confusa sino también contradictoria. Por ejemplo, dices que casi siempre quieres staticy constexprexplicas que son ortogonales e independientes, que hacen cosas diferentes. Luego mencionas una razón para NO combinar los dos, ya que ignoraría el uso de ODR (lo que parece útil). Ah, y todavía no entiendo por qué static debería usarse con constexpr ya que static es para cosas de tiempo de ejecución. Nunca explicaste por qué la estática con constexpr es importante.
void.pointer 01 de
2
@ void.pointer: Tienes razón sobre el último párrafo. Cambié la introducción. Pensé que había explicado la importancia de static constexpr(evita que la matriz constante tenga que recrearse en cada llamada de función), pero modifiqué algunas palabras que podrían aclararlo. Gracias.
rici
8
También podría ser útil mencionar las constantes de tiempo de compilación frente a las constantes de tiempo de ejecución. En otras palabras, si una constexprvariable constante solo se usa en contextos de tiempo de compilación y nunca se necesita en el tiempo de ejecución, entonces staticno tiene sentido, ya que para el momento en que se llega al tiempo de ejecución, el valor ha sido efectivamente "en línea". Sin embargo, si constexprse usa en contextos de tiempo de ejecución (en otras palabras, constexprsería necesario convertirlo constimplícitamente y estar disponible con una dirección física para el código de tiempo de ejecución) querrá staticgarantizar el cumplimiento de ODR, etc. Eso es lo que entiendo, al menos.
void.pointer
3
Un ejemplo para mi último comentario: static constexpr int foo = 100;. No hay ninguna razón por la cual el compilador no pueda sustituir el uso de footodas partes por literal 100, a menos que el código esté haciendo algo así &foo. Así que staticel foono tiene ninguna utilidad en este caso, ya foono existe en tiempo de ejecución. Nuevamente todo depende del compilador.
void.pointer
10

Además de la respuesta dada, vale la pena señalar que no se requiere que el compilador inicialice la constexprvariable en el momento de la compilación, sabiendo que la diferencia entre constexpry static constexprusarla static constexprgarantiza que la variable se inicialice solo una vez.

El siguiente código demuestra cómo la constexprvariable se inicializa varias veces (aunque con el mismo valor), mientras static constexprque seguramente solo se inicializa una vez.

Además, el código compara la ventaja de constexprcontra consten combinación con static.

#include <iostream>
#include <string>
#include <cassert>
#include <sstream>

const short const_short = 0;
constexpr short constexpr_short = 0;

// print only last 3 address value numbers
const short addr_offset = 3;

// This function will print name, value and address for given parameter
void print_properties(std::string ref_name, const short* param, short offset)
{
    // determine initial size of strings
    std::string title = "value \\ address of ";
    const size_t ref_size = ref_name.size();
    const size_t title_size = title.size();
    assert(title_size > ref_size);

    // create title (resize)
    title.append(ref_name);
    title.append(" is ");
    title.append(title_size - ref_size, ' ');

    // extract last 'offset' values from address
    std::stringstream addr;
    addr << param;
    const std::string addr_str = addr.str();
    const size_t addr_size = addr_str.size();
    assert(addr_size - offset > 0);

    // print title / ref value / address at offset
    std::cout << title << *param << " " << addr_str.substr(addr_size - offset) << std::endl;
}

// here we test initialization of const variable (runtime)
void const_value(const short counter)
{
    static short temp = const_short;
    const short const_var = ++temp;
    print_properties("const", &const_var, addr_offset);

    if (counter)
        const_value(counter - 1);
}

// here we test initialization of static variable (runtime)
void static_value(const short counter)
{
    static short temp = const_short;
    static short static_var = ++temp;
    print_properties("static", &static_var, addr_offset);

    if (counter)
        static_value(counter - 1);
}

// here we test initialization of static const variable (runtime)
void static_const_value(const short counter)
{
    static short temp = const_short;
    static const short static_var = ++temp;
    print_properties("static const", &static_var, addr_offset);

    if (counter)
        static_const_value(counter - 1);
}

// here we test initialization of constexpr variable (compile time)
void constexpr_value(const short counter)
{
    constexpr short constexpr_var = constexpr_short;
    print_properties("constexpr", &constexpr_var, addr_offset);

    if (counter)
        constexpr_value(counter - 1);
}

// here we test initialization of static constexpr variable (compile time)
void static_constexpr_value(const short counter)
{
    static constexpr short static_constexpr_var = constexpr_short;
    print_properties("static constexpr", &static_constexpr_var, addr_offset);

    if (counter)
        static_constexpr_value(counter - 1);
}

// final test call this method from main()
void test_static_const()
{
    constexpr short counter = 2;

    const_value(counter);
    std::cout << std::endl;

    static_value(counter);
    std::cout << std::endl;

    static_const_value(counter);
    std::cout << std::endl;

    constexpr_value(counter);
    std::cout << std::endl;

    static_constexpr_value(counter);
    std::cout << std::endl;
}

Posible salida del programa:

value \ address of const is               1 564
value \ address of const is               2 3D4
value \ address of const is               3 244

value \ address of static is              1 C58
value \ address of static is              1 C58
value \ address of static is              1 C58

value \ address of static const is        1 C64
value \ address of static const is        1 C64
value \ address of static const is        1 C64

value \ address of constexpr is           0 564
value \ address of constexpr is           0 3D4
value \ address of constexpr is           0 244

value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0
value \ address of static constexpr is    0 EA0

Como puede ver, constexprse inicia varias veces (la dirección no es la misma), mientras que la staticpalabra clave garantiza que la inicialización se realice solo una vez.

metablaster
fuente
¿No podemos usar constexpr const short constexpr_shortpara dar error si constexpr_short se inicializa de nuevo
akhileshzmishra
su sintaxis constexpr constno tiene sentido porque constexprya lo es const, el constcompilador ignora agregar una o varias veces. Estás intentando detectar un error, pero este no es un error, así es como funciona la mayoría de los compiladores.
metablaster