A variável constexpr estática dentro de uma função faz sentido?

192

Se eu tenho uma variável dentro de uma função (digamos, uma matriz grande), faz sentido declará-la statice constexpr? constexprgarante que a matriz é criada em tempo de compilação, então isso seria staticinútil?

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

Ele está staticrealmente fazendo alguma coisa em termos de código gerado ou semântica?

David Stone
fonte

Respostas:

230

A resposta curta é que não só é staticútil, como sempre será desejável.

Primeiro, observe que statice constexprsão completamente independentes um do outro. staticdefine a vida útil do objeto durante a execução; constexprespecifica que o objeto deve estar disponível durante a compilação. Compilação e execução são disjuntas e descontínuas, no tempo e no espaço. Assim, uma vez que o programa é compilado, constexprnão é mais relevante.

Toda variável declarada constexpré implicitamente, constmas conste staticé quase ortogonal (exceto pela interação com static constnúmeros inteiros).

O C++modelo de objeto (§1.9) exige que todos os objetos que não sejam campos de bits ocupem pelo menos um byte de memória e tenham endereços; além disso, todos esses objetos observáveis ​​em um programa em um determinado momento devem ter endereços distintos (parágrafo 6). Isso não exige muito que o compilador crie uma nova matriz na pilha para cada chamada de uma função com uma matriz const não estática local, porque o compilador pode se refugiar no as-ifprincípio, desde que possa provar que nenhum outro objeto desse tipo pode ser observado.

Infelizmente, isso não será fácil de provar, a menos que a função seja trivial (por exemplo, não chame nenhuma outra função cujo corpo não seja visível na unidade de tradução) porque matrizes, mais ou menos por definição, são endereços. Portanto, na maioria dos casos, a const(expr)matriz não estática precisará ser recriada na pilha a cada chamada, o que anula o ponto de poder computá-la em tempo de compilação.

Por outro lado, um static constobjeto local é compartilhado por todos os observadores e, além disso, pode ser inicializado mesmo que a função na qual ele é definido nunca seja chamada. Portanto, nenhuma das opções acima se aplica, e um compilador é livre não apenas para gerar apenas uma única instância dele; é gratuito gerar uma única instância dele no armazenamento somente leitura.

Então você definitivamente deve usar static constexprno seu exemplo.

No entanto, há um caso em que você não gostaria de usar static constexpr. A menos que um constexprobjeto declarado seja usado ou declarado por ODRstatic , o compilador é livre para não incluí-lo. Isso é bastante útil, pois permite o uso de constexprmatrizes temporárias em tempo de compilação sem poluir o programa compilado com bytes desnecessários. Nesse caso, você claramente não gostaria de usar static, pois staticé provável que force o objeto a existir no tempo de execução.

rici
fonte
2
@AndrewLazarus, você não pode se livrar constde um constobjeto, apenas de um const X*que aponte para um X. Mas esse não é o ponto; o ponto é que objetos automáticos não podem ter endereços estáticos. Como eu disse, constexprdeixa de ser significativo uma vez que a compilação estiver concluída, então não há nada de deitar fora (e muito possivelmente nada, porque o objeto não é ainda garantida a existir em tempo de execução.)
rici
17
Eu meio que sinto que essa resposta não é apenas incrivelmente confusa, mas também auto-contraditória. Por exemplo, você dizer que você quase sempre quer statice constexpr, mas explicar que eles são ortogonais e independente, fazendo coisas diferentes. Você menciona um motivo para NÃO combinar os dois, pois isso ignoraria o uso do ODR (o que parece útil). Ah, e ainda não entendo por que a estática deve ser usada com constexpr, pois estática é para coisas de tempo de execução. Você nunca explicou por que a estática no constexpr é importante.
precisa saber é o seguinte
2
@ void.pointer: Você está certo sobre o último parágrafo. Eu mudei a introdução. Eu pensei que tinha explicado a importância de static constexpr(isso evita que a matriz constante seja recriada a cada chamada de função), mas ajustei algumas palavras que podem torná-lo mais claro. Obrigado.
rici
8
Também pode ser útil mencionar constantes de tempo de compilação versus constantes de tempo de execução. Em outras palavras, se uma constexprvariável constante é usada apenas em contextos de tempo de compilação e nunca é necessária no tempo de execução, staticnão faz sentido, pois no momento em que você acessa o tempo de execução, o valor foi efetivamente "incorporado". No entanto, se constexprfor usado em contextos de tempo de execução (em outras palavras, constexprprecisaria ser convertido em constimplicitamente e disponível com um endereço físico para código de tempo de execução), ele desejará staticgarantir a conformidade com o ODR, etc. Esse é o meu entendimento, pelo menos.
void.pointer
3
Um exemplo para o meu último comentário: static constexpr int foo = 100;. Não há razão para que o compilador não possa substituir fooliteralmente o uso de qualquer lugar 100, a menos que o código esteja fazendo algo parecido &foo. Então, staticno footem nenhuma utilidade, neste caso, uma vez que foonão existe em tempo de execução. Novamente, tudo depende do compilador.
void.pointer
10

Além da resposta dada, vale a pena notar que o compilador não precisa inicializar a constexprvariável no momento da compilação, sabendo que a diferença entre constexpre static constexpré a sua utilização static constexprgarante que a variável seja inicializada apenas uma vez.

O código a seguir demonstra como a constexprvariável é inicializada várias vezes (embora com o mesmo valor), enquanto static constexprcertamente é inicializada apenas uma vez.

Além disso, o código compara a vantagem de constexprcontra constem combinação com 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;
}

Saída possível do 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 você pode ver, ele constexpré iniciado várias vezes (o endereço não é o mesmo), enquanto a staticpalavra-chave garante que a inicialização seja realizada apenas uma vez.

metablaster
fonte
não podemos usar constexpr const short constexpr_shortpara dar erro se constexpr_short for inicializado novamente
akhileshzmishra
sua sintaxe de constexpr constnão faz sentido, porque constexprjá é const, adicionar constuma ou várias vezes é ignorado pelo compilador. Você está tentando capturar um erro, mas não é um erro, é assim que a maioria dos compiladores funciona.
metablaster